Projects STRLCPY GraphSpy Commits d49b46c2
🤬
  • Added custom requests with headers, variables and request templates.

  • Loading...
  • RedByte1337 committed 2 months ago
    d49b46c2
    1 parent 8433c28b
  • ■ ■ ■ ■ ■ ■
    GraphSpy/GraphSpy.py
    skipped 5 lines
    6 6  import sqlite3
    7 7  from datetime import datetime, timezone
    8 8  import time
    9  -import os,sys,shutil
     9 +import os,sys,shutil,traceback
    10 10  from threading import Thread
    11 11  import json
    12 12  import uuid
    skipped 8 lines
    21 21   con.execute('CREATE TABLE accesstokens (id INTEGER PRIMARY KEY AUTOINCREMENT, stored_at TEXT, issued_at TEXT, expires_at TEXT, description TEXT, user TEXT, resource TEXT, accesstoken TEXT)')
    22 22   con.execute('CREATE TABLE refreshtokens (id INTEGER PRIMARY KEY AUTOINCREMENT, stored_at TEXT, description TEXT, user TEXT, tenant_id TEXT, resource TEXT, foci INTEGER, refreshtoken TEXT)')
    23 23   con.execute('CREATE TABLE devicecodes (id INTEGER PRIMARY KEY AUTOINCREMENT, generated_at INTEGER, expires_at INTEGER, user_code TEXT, device_code TEXT, interval INTEGER, client_id TEXT, status TEXT, last_poll INTEGER)')
     24 + con.execute('CREATE TABLE request_templates (id INTEGER PRIMARY KEY AUTOINCREMENT, template_name TEXT, uri TEXT, method TEXT, request_type TEXT, body TEXT, headers TEXT, variables TEXT)')
    24 25   con.execute('CREATE TABLE settings (setting TEXT UNIQUE, value TEXT)')
    25 26   # Valid Settings: active_access_token_id, active_refresh_token_id, schema_version
    26 27   cur = con.cursor()
    27  - cur.execute("INSERT INTO settings (setting, value) VALUES ('schema_version', '1')")
     28 + cur.execute("INSERT INTO settings (setting, value) VALUES ('schema_version', '2')")
    28 29   con.commit()
    29 30   con.close()
    30 31   
    skipped 40 lines
    71 72   } for db_file in db_folder_content if db_file.is_file() and db_file.name.endswith(".db")]
    72 73   return databases
    73 74   
     75 +def update_db():
     76 + latest_schema_version = "2"
     77 + current_schema_version = query_db("SELECT value FROM settings where setting = 'schema_version'",one=True)[0]
     78 + if current_schema_version == "1":
     79 + print("[*] Current database is schema version 1, updating to schema version 2")
     80 + execute_db("CREATE TABLE request_templates (id INTEGER PRIMARY KEY AUTOINCREMENT, template_name TEXT, uri TEXT, method TEXT, request_type TEXT, body TEXT, headers TEXT, variables TEXT)")
     81 + execute_db("UPDATE settings SET value = '2' WHERE setting = 'schema_version'")
     82 + print("[*] Updated database to schema version 2")
     83 + 
    74 84  # ========== Helper Functions ==========
    75 85   
    76 86  def graph_request(graph_uri, access_token_id):
    skipped 5 lines
    82 92   
    83 93  def graph_request_post(graph_uri, access_token_id, body):
    84 94   access_token = query_db("SELECT accesstoken FROM accesstokens where id = ?",[access_token_id],one=True)[0]
    85  - headers = {"Authorization":f"Bearer {access_token}"}
     95 + headers = {"Authorization":f"Bearer {access_token}"}
    86 96   response = requests.post(graph_uri, headers=headers, json=body)
    87 97   resp_json = response.json()
    88 98   return json.dumps(resp_json)
    89 99  
     100 +def generic_request(uri, access_token_id, method, request_type, body, headers):
     101 + access_token = query_db("SELECT accesstoken FROM accesstokens where id = ?",[access_token_id],one=True)[0]
     102 + headers["Authorization"] = f"Bearer {access_token}"
     103 + 
     104 + # Empty body
     105 + if not body:
     106 + response = requests.request(method, uri, headers=headers)
     107 + # Text, XML or urlencoded request
     108 + elif request_type in ["text", "urlencoded", "xml"]:
     109 + if request_type == "urlencoded" and not "Content-Type" in headers:
     110 + headers["Content-Type"] = "application/x-www-form-urlencoded"
     111 + if request_type == "xml" and not "Content-Type" in headers:
     112 + headers["Content-Type"] = "application/xml"
     113 + response = requests.request(method, uri, headers=headers, data=body)
     114 + # Json request
     115 + elif request_type == "json":
     116 + try:
     117 + response = requests.request(method, uri, headers=headers, json=json.loads(body))
     118 + except ValueError as e:
     119 + return f"[Error] The body message does not contain valid JSON, but a body type of JSON was specified.", 400
     120 + else:
     121 + return f"[Error] Invalid request type.", 400
     122 + 
     123 + # Format json if the Content-Type contains json
     124 + response_type = "json" if ("Content-Type" in response.headers and "json" in response.headers["Content-Type"]) else "xml" if ("Content-Type" in response.headers and "xml" in response.headers["Content-Type"]) else "text"
     125 + try:
     126 + response_text = json.dumps(response.json()) if response_type == "json" else response.text
     127 + except ValueError as e:
     128 + response_text = response.text
     129 + return {"response_status_code": response.status_code ,"response_type": response_type ,"response_text": response_text}
     130 + 
    90 131  def save_access_token(accesstoken, description):
    91 132   decoded_accesstoken = jwt.decode(accesstoken, options={"verify_signature": False})
    92 133   user = "unknown"
    skipped 201 lines
    294 335   def device_codes():
    295 336   return render_template('device_codes.html', title="Device Codes")
    296 337   
    297  - @app.route("/graph_requests")
    298  - def graph_requests():
    299  - return render_template('requests.html', title="Graph Requests")
     338 + @app.route("/custom_requests")
     339 + def custom_requests():
     340 + return render_template('custom_requests.html', title="Custom Requests")
    300 341   
    301 342   @app.route("/generic_search")
    302 343   def generic_search():
    303  - return render_template('generic_search.html', title="Generic Search")
     344 + return render_template('generic_search.html', title="Generic MSGraph Search")
    304 345   
    305 346   @app.route("/recent_files")
    306 347   def recent_files():
    skipped 159 lines
    466 507   active_access_token = query_db("SELECT value FROM settings WHERE setting = 'active_access_token_id'",one=True)
    467 508   return f"{active_access_token[0]}" if active_access_token else "0"
    468 509  
    469  - # ========== Graph Requests ==========
     510 + # ========== Generic Requests ==========
    470 511   
    471 512   @app.post("/api/generic_graph")
    472 513   def api_generic_graph():
    skipped 10 lines
    483 524   graph_response = graph_request_post(graph_uri, access_token_id, body)
    484 525   return graph_response
    485 526   
     527 + @app.post("/api/custom_api_request")
     528 + def api_custom_api_request():
     529 + if not request.is_json:
     530 + return f"[Error] Expecting JSON input.", 400
     531 + request_json = request.get_json()
     532 + print(request_json)
     533 + uri = request_json['uri'] if 'uri' in request_json else ''
     534 + access_token_id = request_json['access_token_id'] if 'access_token_id' in request_json else 0
     535 + method = request_json['method'] if 'method' in request_json else 'GET'
     536 + request_type = request_json['request_type'] if 'request_type' in request_json else 'text'
     537 + body = request_json['body'] if 'body' in request_json else ''
     538 + headers = request_json['headers'] if 'headers' in request_json else {}
     539 + variables = request_json['variables'] if 'variables' in request_json else {}
     540 + 
     541 + if not (uri and access_token_id and method):
     542 + return f"[Error] URI, Access Token ID and Method are mandatory!", 400
     543 + elif request_type not in ["text", "json", "urlencoded", "xml"]:
     544 + return f"[Error] Invalid request type '{request_type}'. Should be one of the following values: text, json, urlencoded, xml", 400
     545 + elif type(headers) != dict or type(variables) != dict:
     546 + return f"[Error] Expecting json input for headers and variables. Received '{type(headers)}' and '{type(variables)}' respectively.", 400
     547 + 
     548 + for variable_name, variable_value in variables.items():
     549 + uri = uri.replace(variable_name, variable_value)
     550 + body = body.replace(variable_name, variable_value)
     551 + temp_headers = {}
     552 + for header_name, header_value in headers.items():
     553 + new_header_name = header_name.replace(variable_name, variable_value) if type(header_name) == str else header_name
     554 + new_header_value = header_value.replace(variable_name, variable_value) if type(header_value) == str else header_value
     555 + temp_headers[new_header_name] =new_header_value
     556 + headers = temp_headers
     557 + try:
     558 + api_response = generic_request(uri, access_token_id, method, request_type, body, headers)
     559 + except Exception as e:
     560 + traceback.print_exc()
     561 + return f"[Error] Unexpected error occurred. Check your input for any issues. Exception: {repr(e)}", 400
     562 + return api_response
     563 +
     564 + @app.post("/api/save_request_template")
     565 + def api_save_request_template():
     566 + if not request.is_json:
     567 + return f"[Error] Expecting JSON input.", 400
     568 + request_json = request.get_json()
     569 + print(request_json)
     570 + template_name = request_json['template_name'] if 'template_name' in request_json else ''
     571 + uri = request_json['uri'] if 'uri' in request_json else ''
     572 + method = request_json['method'] if 'method' in request_json else 'GET'
     573 + request_type = request_json['request_type'] if 'request_type' in request_json else 'text'
     574 + body = request_json['body'] if 'body' in request_json else ''
     575 + headers = request_json['headers'] if 'headers' in request_json else {}
     576 + variables = request_json['variables'] if 'variables' in request_json else {}
     577 + 
     578 + if not (template_name and uri and method):
     579 + return f"[Error] Template Name, URI and Method are mandatory!", 400
     580 + elif request_type not in ["text", "json", "urlencoded", "xml"]:
     581 + return f"[Error] Invalid request type '{request_type}'. Should be one of the following values: text, json, urlencoded, xml", 400
     582 + elif type(headers) != dict or type(variables) != dict:
     583 + return f"[Error] Expecting json input for headers and variables. Received '{type(headers)}' and '{type(variables)}' respectively.", 400
     584 +
     585 + template_exists = False
     586 + try:
     587 + # If a request template with the same name already exists, delete it first
     588 + existing_request_template = query_db_json("SELECT * FROM request_templates WHERE template_name = ?",[template_name],one=True)
     589 + if existing_request_template:
     590 + template_exists = True
     591 + execute_db("DELETE FROM request_templates where id = ?",[existing_request_template["id"]])
     592 + # Save the new request template
     593 + execute_db("INSERT INTO request_templates (template_name, uri, method, request_type, body, headers, variables) VALUES (?,?,?,?,?,?,?)",(
     594 + template_name,
     595 + uri,
     596 + method,
     597 + request_type,
     598 + body,
     599 + json.dumps(headers),
     600 + json.dumps(variables)
     601 + )
     602 + )
     603 + except Exception as e:
     604 + traceback.print_exc()
     605 + return f"[Error] Unexpected error occurred. Check your input for any issues. Exception: {repr(e)}", 400
     606 + if template_exists:
     607 + return f"[Success] Updated configuration for request template '{template_name}'."
     608 + return f"[Success] Saved template '{template_name}' to database."
     609 +
     610 + @app.route("/api/get_request_templates/<template_id>")
     611 + def api_request_templates(template_id):
     612 + request_template = query_db_json("SELECT * FROM request_templates WHERE id = ?",[template_id],one=True)
     613 + if request_template:
     614 + request_template['headers'] = json.loads(request_template['headers'])
     615 + request_template['variables'] = json.loads(request_template['variables'])
     616 + if not request_template:
     617 + return f"[Error] Unable to find request template with ID '{template_id}'.", 400
     618 + return request_template
     619 + 
     620 + @app.route("/api/list_request_templates")
     621 + def api_list_request_templates():
     622 + request_templates = query_db_json("SELECT * FROM request_templates")
     623 + print(request_templates)
     624 + for i in range(len(request_templates)):
     625 + request_templates[i]['headers'] = json.loads( request_templates[i]['headers'])
     626 + request_templates[i]['variables'] = json.loads(request_templates[i]['variables'])
     627 + return request_templates
     628 +
     629 + @app.post("/api/delete_request_template")
     630 + def api_delete_request_template():
     631 + if not "template_id" in request.form:
     632 + return f"[Error] No template_id specified.", 400
     633 + template_id = request.form['template_id']
     634 + existing_request_template = query_db_json("SELECT * FROM request_templates WHERE id = ?",[template_id],one=True)
     635 + if not existing_request_template:
     636 + return f"[Error] Unable to find request template with ID '{template_id}'.", 400
     637 + execute_db("DELETE FROM request_templates where id = ?",[template_id])
     638 + return f"[Success] Deleted request template '{existing_request_template['template_name']}' from database."
     639 + 
     640 + 
    486 641   # ========== Database ==========
    487 642  
    488 643   @app.get("/api/list_databases")
    skipped 26 lines
    515 670   if(not os.path.exists(db_path)):
    516 671   return f"[Error] Database file '{db_path}' not found."
    517 672   app.config['graph_spy_db_path'] = db_path
     673 + update_db()
    518 674   return f"[Success] Activated database '{database_name}'."
    519 675  
    520 676   @app.post("/api/duplicate_database")
    skipped 103 lines
    624 780   if(not os.path.exists(graph_spy_db_path)):
    625 781   sys.exit(f"Failed creating database file at '{graph_spy_db_path}'. Unable to proceed.")
    626 782   print(f"[*] Utilizing database '{graph_spy_db_path}'.")
    627  -
     783 + # Update the database to the latest schema version if required
     784 + with app.app_context():
     785 + update_db()
    628 786   # Disable datatable error messages by default.
    629 787   app.config['table_error_messages'] = "disabled"
    630  - 
    631 788   # Run flask
    632 789   print(f"[*] Starting GraphSpy. Open in your browser by going to the url displayed below.\n")
    633 790   app.run(debug=args.debug, host=args.interface, port=args.port)
    skipped 3 lines
  • ■ ■ ■ ■ ■ ■
    GraphSpy/static/js/functions.js
    skipped 241 lines
    242 242   $('table.dataTable').DataTable().ajax.reload(null, false);
    243 243  }
    244 244   
     245 +function prettifyXml(sourceXml) {
     246 + var xmlDoc = new DOMParser().parseFromString(sourceXml, 'application/xml');
     247 + var xsltDoc = new DOMParser().parseFromString([
     248 + // describes how we want to modify the XML - indent everything
     249 + '<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">',
     250 + ' <xsl:strip-space elements="*"/>',
     251 + ' <xsl:template match="para[content-style][not(text())]">', // change to just text() to strip space in text nodes
     252 + ' <xsl:value-of select="normalize-space(.)"/>',
     253 + ' </xsl:template>',
     254 + ' <xsl:template match="node()|@*">',
     255 + ' <xsl:copy><xsl:apply-templates select="node()|@*"/></xsl:copy>',
     256 + ' </xsl:template>',
     257 + ' <xsl:output indent="yes"/>',
     258 + '</xsl:stylesheet>',
     259 + ].join('\n'), 'application/xml');
     260 + 
     261 + var xsltProcessor = new XSLTProcessor();
     262 + xsltProcessor.importStylesheet(xsltDoc);
     263 + var resultDoc = xsltProcessor.transformToDocument(xmlDoc);
     264 + var resultXml = new XMLSerializer().serializeToString(resultDoc);
     265 + if (resultXml.includes("parsererror")) {
     266 + return sourceXml;
     267 + }
     268 + return resultXml;
     269 +};
     270 + 
    245 271  // ========== Messages ==========
    246 272   
    247 273  function bootstrapAlert(message, type) {
    skipped 32 lines
  • ■ ■ ■ ■ ■ ■
    GraphSpy/templates/custom_requests.html
     1 +{% extends 'layout.html'%}
     2 + 
     3 +{%block content%}
     4 + 
     5 +<br>
     6 +<div class="col-md-11">
     7 + <h1>Custom Requests</h1>
     8 + <form id="custom_request_form" class="row g-3">
     9 + <div class="col-2">
     10 + <label for="method" class="form-label">Method *</label>
     11 + <input list="methods" id="method" placeholder="GET" class="form-control" required>
     12 + <datalist id="methods">
     13 + <option value="GET">GET</option>
     14 + <option value="POST">POST</option>
     15 + <option value="PUT">PUT</option>
     16 + <option value="PATCH">PATCH</option>
     17 + <option value="HEAD">HEAD</option>
     18 + <option value="DELETE">DELETE</option>
     19 + <option value="OPTIONS">OPTIONS</option>
     20 + </datalist>
     21 + </div>
     22 + <div class="col-2">
     23 + <label for="request_type" class="form-label">Body type</label>
     24 + <select id="request_type" class="form-select" aria-label="Request body">
     25 + <option value="text">Text</option>
     26 + <option value="urlencoded">URL Encoded</option>
     27 + <option value="json">JSON</option>
     28 + <option value="xml">XML</option>
     29 + </select>
     30 + </div>
     31 + <div class="col-2">
     32 + <label for="access_token_id" class="form-label">Access token id *</label>
     33 + <input type="text" id="access_token_id" name="access_token_id" class="form-control" required>
     34 + </div>
     35 + <div class="col-3"></div>
     36 + <div class="col-3">
     37 + <label for="request_template_name" class="form-label">Request Template</label>
     38 + <div class="input-group">
     39 + <input type="text" id="request_template_name" placeholder="Template Name" class="form-control">
     40 + <button class="btn btn-outline-primary" type="button" onclick="saveRequestTemplate()">Save</button>
     41 + <button class="btn btn-outline-primary" type="button" data-bs-toggle="modal" data-bs-target="#request_template_modal" onclick="$('#request_templates').DataTable().ajax.reload(null, false)">Load...</button>
     42 + </div>
     43 + </div>
     44 + <div>
     45 + <label for="request_uri" class="form-label">Request Uri *</label>
     46 + <input type="text" id="request_uri" placeholder="https://graph.microsoft.com/v1.0/me" class="form-control" required>
     47 + </div>
     48 + <div>
     49 + <button class="btn btn-outline-success" type="button" onclick="addHeaderRow()">Add Header</button>
     50 + <button class="btn btn-outline-success" type="button" onclick="addVariableRow()">Add Variable</button>
     51 + </div>
     52 + <div class="col-6" id="header_fields">
     53 + <div class="input-group">
     54 + <span class="input-group-text">Header</span>
     55 + <input type="text" placeholder="X-Header" class="form-control header_name">
     56 + <input type="text" placeholder="Custom" class="form-control header_value">
     57 + <button class="btn btn-outline-danger" type="button" onclick="return this.parentNode.remove()">Delete</button>
     58 + </div>
     59 + </div>
     60 + <div class="col-6" id="variable_fields">
     61 + <div class="input-group">
     62 + <span class="input-group-text">Variable</span>
     63 + <input type="text" placeholder="$$example_id$$" class="form-control variable_name">
     64 + <input type="text" placeholder="01234567-89ab-cdef-0123-456789abcdef" class="form-control variable_value">
     65 + <button class="btn btn-outline-danger" type="button" onclick="return this.parentNode.remove()">Delete</button>
     66 + </div>
     67 + </div>
     68 + <div>
     69 + <button class="btn btn-outline-primary" type="button" onclick="formatBodyJSON()">Format JSON</button>
     70 + <button class="btn btn-outline-primary" type="button" onclick="formatBodyXML()">Format XML</button>
     71 + </div>
     72 + <div class="form-floating">
     73 + <textarea type="text" id="body" class="form-control" placeholder="Body" style="height: 10em"></textarea>
     74 + <label for="body">Body</label>
     75 + </div>
     76 + <div>
     77 + <button type="Button" class="btn btn-primary" onclick="sendGenerateRequest()">Request</button>
     78 + </div>
     79 + </form>
     80 + <script>
     81 + getActiveAccessToken(document.getElementById("custom_request_form").access_token_id)
     82 + </script>
     83 +</div>
     84 +<br>
     85 +<div class="row" id="response-card" style="display: none">
     86 + <div class="col-auto">
     87 + <div class="card mt-3">
     88 + <div class="card-header" style="text-align: center">
     89 + <b>Response [<span id="response_status_code"></span>]</b>
     90 + <i class="fi fi-rr-copy-alt" id="copy-icon" style="cursor: pointer; float: right"></i>
     91 + </div>
     92 + <div class="card-body">
     93 + <pre id="response_body" class="mb-0"></pre>
     94 + </div>
     95 + </div>
     96 + </div>
     97 +</div>
     98 +<!-- Modal -->
     99 +<div class="modal fade" id="request_template_modal" tabindex="-1" aria-labelledby="request_template_modal_label" aria-hidden="true">
     100 + <div class="modal-dialog modal-xl">
     101 + <div class="modal-content">
     102 + <div class="modal-header">
     103 + <h1 class="modal-title fs-5" id="request_template_modal_label">Request Templates</h1>
     104 + <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
     105 + </div>
     106 + <div class="modal-body">
     107 + <div id="dTable" class="box-body table-responsive" style="padding:20px;">
     108 + <table id="request_templates" class="display" style="width:100%">
     109 + <thead>
     110 + <tr>
     111 + <th></th>
     112 + <th></th>
     113 + <th></th>
     114 + <th>ID</th>
     115 + <th>Name</th>
     116 + <th>Body type</th>
     117 + <th>Method</th>
     118 + <th>URI</th>
     119 + </tr>
     120 + </thead>
     121 + </table>
     122 + </div>
     123 + </div>
     124 + <div class="modal-footer">
     125 + <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
     126 + </div>
     127 + </div>
     128 + </div>
     129 +</div>
     130 +<script type="text/javascript" class="init">
     131 + // Populate the request_templates table
     132 + let myTable = new DataTable('#request_templates', {
     133 + ajax: {
     134 + url: '/api/list_request_templates', dataSrc: ""
     135 + },
     136 + columns: [
     137 + {
     138 + className: 'dt-control',
     139 + orderable: false,
     140 + data: null,
     141 + defaultContent: '',
     142 + 'width': '20px'
     143 + },
     144 + {
     145 + className: 'active-control',
     146 + orderable: false,
     147 + data: null,
     148 + defaultContent: '<i class="fi fi-br-check" style="cursor: pointer"></i>',
     149 + 'width': '20px'
     150 + },
     151 + {
     152 + className: 'delete-control',
     153 + orderable: false,
     154 + data: null,
     155 + defaultContent: '<i class="fi fi-rr-trash" style="cursor: pointer"></i>',
     156 + 'width': '20px'
     157 + },
     158 + { data: 'id', 'width': '30px' },
     159 + { data: 'template_name', 'width': '150px' },
     160 + { data: 'request_type', 'width': '150px' },
     161 + { data: 'method', 'width': '100px' },
     162 + { data: 'uri'}
     163 + ],
     164 + order: [[3, 'desc']]
     165 + })
     166 + 
     167 + myTable.on('click', 'td.dt-control', function (e) {
     168 + let tr = e.target.closest('tr');
     169 + let row = myTable.row(tr);
     170 + 
     171 + if (row.child.isShown()) {
     172 + // This row is already open - close it
     173 + row.child.hide();
     174 + }
     175 + else {
     176 + // Open this row
     177 + row.child(format(row.data())).show();
     178 + }
     179 + });
     180 + 
     181 + myTable.on('click', 'td.active-control', function (e) {
     182 + let tr = e.target.closest('tr');
     183 + let row = myTable.row(tr);
     184 + request_template_info = row.data();
     185 + $("#request_uri").val(request_template_info.uri);
     186 + $("#method").val(request_template_info.method);
     187 + $("#request_type").val(request_template_info.request_type);
     188 + $("#body").val(request_template_info.body);
     189 + $("#request_template_name").val(request_template_info.template_name);
     190 + $('#header_fields').empty();
     191 + $('#variable_fields').empty();
     192 + for (var header_name in request_template_info.headers) {
     193 + if (request_template_info.headers.hasOwnProperty(header_name)) {
     194 + addHeaderRow(header_name, request_template_info.headers[header_name]);
     195 + }
     196 + }
     197 + for (var variable_name in request_template_info.variables) {
     198 + if (request_template_info.variables.hasOwnProperty(variable_name)) {
     199 + addVariableRow(variable_name, request_template_info.variables[variable_name]);
     200 + }
     201 + }
     202 + $('#request_template_modal').modal('hide');
     203 + });
     204 + 
     205 + myTable.on('click', 'td.delete-control', function (e) {
     206 + let tr = e.target.closest('tr');
     207 + let row = myTable.row(tr);
     208 + if (!confirm("Are you sure you want to delete request template '" + row.data().template_name + "'?")) { return }
     209 + deleteRequestTemplate(row.data().id);
     210 + });
     211 + 
     212 + function format(d) {
     213 + var descList = $(
     214 + '<dl>' +
     215 + '<dt>Request Template:</dt>' +
     216 + '</dl>');
     217 + var descDetails = $('<dd></dd>');
     218 + var preText = $('<pre></pre>');
     219 + preText.text(JSON.stringify(d, undefined, 4));
     220 + descDetails.append(preText);
     221 + descList.append(descDetails);
     222 + return descList
     223 + }
     224 + 
     225 + function deleteRequestTemplate(id) {
     226 + let response = $.ajax({
     227 + type: "POST",
     228 + async: false,
     229 + url: "/api/delete_request_template",
     230 + data: {"template_id": id}
     231 + });
     232 + $('#request_templates').DataTable().ajax.reload(null, false);
     233 + bootstrapToast("Delete Request Template", response.responseText)
     234 + }
     235 +</script>
     236 +<script>
     237 + function getHeaders() {
     238 + let headers = {};
     239 + for (const header of $("#header_fields").children()) {
     240 + if (header.getElementsByClassName("header_name")[0].value != "") {
     241 + headers[header.getElementsByClassName("header_name")[0].value] = header.getElementsByClassName("header_value")[0].value;
     242 + };
     243 + };
     244 + return headers;
     245 + }
     246 + function getVariables() {
     247 + let variables = {};
     248 + for (const variable of $("#variable_fields").children()) {
     249 + if (variable.getElementsByClassName("variable_name")[0].value != "") {
     250 + variables[variable.getElementsByClassName("variable_name")[0].value] = variable.getElementsByClassName("variable_value")[0].value;
     251 + };
     252 + };
     253 + return variables;
     254 + }
     255 + 
     256 + function sendGenerateRequest() {
     257 + let request_form = document.getElementById("custom_request_form");
     258 + let response;
     259 + response = $.ajax({
     260 + type: "POST",
     261 + async: false,
     262 + url: "/api/custom_api_request",
     263 + dataType: "json",
     264 + contentType: "application/json; charset=utf-8",
     265 + data: JSON.stringify({
     266 + "uri": request_form.request_uri.value,
     267 + "access_token_id": request_form.access_token_id.value,
     268 + "method": request_form.method.value,
     269 + "request_type": request_form.request_type.value,
     270 + "body": request_form.body.value,
     271 + "headers": getHeaders(),
     272 + "variables": getVariables()
     273 + }),
     274 + success: null
     275 + });
     276 + if (response.status >= 400) {
     277 + bootstrapAlert(response.responseText, "danger");
     278 + return;
     279 + }
     280 + let responseJSON = JSON.parse(response.responseText);
     281 + if (responseJSON.response_type == "json") {
     282 + $("#response_body").text(JSON.stringify(responseJSON.response_text, undefined, 4));
     283 + } else if (responseJSON.response_type == "xml") {
     284 + $("#response_body").text(prettifyXml(responseJSON.response_text));
     285 + } else {
     286 + $("#response_body").text(responseJSON.response_text);
     287 + }
     288 + $("#response_status_code").text(responseJSON.response_status_code);
     289 + $("#response-card").show();
     290 + }
     291 + 
     292 + function saveRequestTemplate() {
     293 + let request_form = document.getElementById("custom_request_form");
     294 + let response;
     295 + // request_template_name
     296 + response = $.ajax({
     297 + type: "POST",
     298 + async: false,
     299 + url: "/api/save_request_template",
     300 + dataType: "json",
     301 + contentType: "application/json; charset=utf-8",
     302 + data: JSON.stringify({
     303 + "template_name": $("#request_template_name").val(),
     304 + "uri": request_form.request_uri.value,
     305 + "method": request_form.method.value,
     306 + "request_type": request_form.request_type.value,
     307 + "body": request_form.body.value,
     308 + "headers": getHeaders(),
     309 + "variables": getVariables()
     310 + }),
     311 + success: null
     312 + });
     313 + if (response.status >= 400) {
     314 + bootstrapAlert(response.responseText, "danger");
     315 + return;
     316 + }
     317 + bootstrapToast("Save Request Template", response.responseText)
     318 + }
     319 + 
     320 + function formatBodyJSON() {
     321 + try {
     322 + $("#body").val(JSON.stringify(JSON.parse($("#body").val()), undefined, 4));
     323 + } catch (e) {
     324 + bootstrapToast("Format JSON", "[Error] Request body is not valid JSON.")
     325 + }
     326 + }
     327 + 
     328 + function formatBodyXML() {
     329 + try {
     330 + $("#body").val(prettifyXml($("#body").val()), undefined, 4);
     331 + } catch (e) {
     332 + bootstrapToast("Format XML", "[Error] Request body is not valid XML.")
     333 + }
     334 + }
     335 + 
     336 + function addHeaderRow(headerName = "", headerValue = "") {
     337 + var headerWrapper = $('<div class="input-group">' +
     338 + '<span class="input-group-text">Header</span>' +
     339 + '</div>');
     340 + var headerNameInput = $('<input type="text" placeholder="X-Header" class="form-control header_name">');
     341 + headerNameInput.val(headerName);
     342 + var headerValueInput = $('<input type="text" placeholder="Custom" class="form-control header_value">');
     343 + headerValueInput.val(headerValue);
     344 + var headerButton = $('<button class="btn btn-outline-danger" type="button" onclick="return this.parentNode.remove()">Delete</button>');
     345 + 
     346 + headerWrapper.append(headerNameInput);
     347 + headerWrapper.append(headerValueInput);
     348 + headerWrapper.append(headerButton);
     349 + $("#header_fields").append(headerWrapper);
     350 + }
     351 + function addVariableRow(variableName = "", variableValue = "") {
     352 + var variableWrapper = $('<div class="input-group">' +
     353 + '<span class="input-group-text">Variable</span>' +
     354 + '</div>');
     355 + var variableNameInput = $('<input type="text" placeholder="$$example_id$$" class="form-control variable_name">');
     356 + variableNameInput.val(variableName);
     357 + var variableNameValue = $('<input type="text" placeholder="01234567-89ab-cdef-0123-456789abcdef" class="form-control variable_value">');
     358 + variableNameValue.val(variableValue);
     359 + var variableButton = $('<button class="btn btn-outline-danger" type="button" onclick="return this.parentNode.remove()">Delete</button>');
     360 + 
     361 + variableWrapper.append(variableNameInput);
     362 + variableWrapper.append(variableNameValue);
     363 + variableWrapper.append(variableButton);
     364 + $("#variable_fields").append(variableWrapper);
     365 + }
     366 + 
     367 + $("#response-card").on('click', 'i#copy-icon', function (e) {
     368 + copyToClipboard($("#response_body").text());
     369 + })
     370 + 
     371 +</script>
     372 + {%endblock content%}
     373 + 
  • ■ ■ ■ ■ ■ ■
    GraphSpy/templates/layout.html
    skipped 50 lines
    51 51   Graph
    52 52   </a>
    53 53   <ul class="dropdown-menu">
    54  - <li><a class="dropdown-item" href="{{url_for('graph_requests')}}">Generic Graph Requests</a></li>
    55  - <li><a class="dropdown-item" href="{{url_for('generic_search')}}">Generic Search</a></li>
     54 + <li><a class="dropdown-item" href="{{url_for('custom_requests')}}">Custom Requests</a></li>
     55 + <li><a class="dropdown-item" href="{{url_for('generic_search')}}">Generic MSGraph Search</a></li>
    56 56   </ul>
    57 57   </li>
    58 58   <li class="nav-item dropdown">
    skipped 228 lines
  • ■ ■ ■ ■ ■ ■
    GraphSpy/templates/requests.html
    1  -{% extends 'layout.html'%}
    2  - 
    3  -{%block content%}
    4  - 
    5  -<br>
    6  -<div class="col-md-11">
    7  - <h1>Generic Graph Request</h1>
    8  - <form id="graph_generic_form" class="row g-3">
    9  - <div class="col-3">
    10  - <label for="method" class="form-label">Method *</label>
    11  - <input list="method" name="method" value="GET" class="form-control" required>
    12  - <datalist name="method" id="method">
    13  - <option value="GET">GET</option>
    14  - <option value="POST">POST</option>
    15  - </datalist>
    16  - </div>
    17  - <div class="col-3">
    18  - <label for="access_token_id" class="form-label">Access token id *</label>
    19  - <input type="text" id="access_token_id" name="access_token_id" class="form-control" required>
    20  - </div>
    21  - <div>
    22  - <label for="graph_uri" class="form-label">Graph Uri *</label>
    23  - <input type="text" id="graph_uri" name="graph_uri" value="https://graph.microsoft.com/v1.0/" class="form-control" required>
    24  - </div>
    25  - <div>
    26  - <label for="body" class="form-label">Body</label>
    27  - <textarea type="text" id="body" name="body" value="" rows=5 class="form-control">{"requests": [{"entityTypes": ["drive"], "query": {"queryString": "*"}, "from": 0, "size": 10}]}</textarea>
    28  - </div>
    29  - <div>
    30  - <button type="Button" class="btn btn-primary" onclick="generateRequest()">Request</button>
    31  - </div>
    32  - </form>
    33  - <script>
    34  - getActiveAccessToken(document.getElementById("graph_generic_form").access_token_id)
    35  - </script>
    36  -</div>
    37  -<br>
    38  -<div class="row" id="response-card" style="display: none">
    39  - <div class="col-auto">
    40  - <div class="card mt-3">
    41  - <div class="card-header" style="text-align: center">
    42  - <b>Response</b>
    43  - <i class="fi fi-rr-copy-alt" id="copy-icon" style="cursor: pointer; float: right"></i>
    44  - </div>
    45  - <div class="card-body">
    46  - <pre id="response_json" class="mb-0"></pre>
    47  - </div>
    48  - </div>
    49  - </div>
    50  -</div>
    51  -<script>
    52  - function generateRequest() {
    53  - let graph_form = document.getElementById("graph_generic_form");
    54  - let response;
    55  - if (graph_form.method.value == "GET") {
    56  - response = $.ajax({
    57  - type: "POST",
    58  - async: false,
    59  - url: "/api/generic_graph",
    60  - dataSrc: "",
    61  - data: { "graph_uri": graph_form.graph_uri.value, "access_token_id": graph_form.access_token_id.value },
    62  - });
    63  - } else if (graph_form.method.value == "POST") {
    64  - response = $.ajax({
    65  - type: "POST",
    66  - async: false,
    67  - url: "/api/generic_graph_post",
    68  - dataSrc: "",
    69  - data: { "graph_uri": graph_form.graph_uri.value, "access_token_id": graph_form.access_token_id.value, "body": graph_form.body.value },
    70  - });
    71  - } else {
    72  - alert("Invalid method.");
    73  - return false;
    74  - }
    75  - let responseJSON = JSON.parse(response.responseText);
    76  - $("#response_json").text(JSON.stringify(responseJSON, undefined, 4))
    77  - $("#response-card").show()
    78  - }
    79  - $("#response-card").on('click', 'i#copy-icon', function (e) {
    80  - copyToClipboard($("#response_json").text())
    81  - })
    82  -</script>
    83  - {%endblock content%}
    84  - 
  • ■ ■ ■ ■
    GraphSpy/version.txt
    1  -1.0.3
     1 +1.1.0
Please wait...
Page is in error, reload to recover