pywebexec 1.9.20__py3-none-any.whl → 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pywebexec/pywebexec.py CHANGED
@@ -28,7 +28,6 @@ import logging
28
28
  from pathlib import Path
29
29
  import pyte
30
30
  from . import host_ip
31
- from flask_swagger_ui import get_swaggerui_blueprint # new import
32
31
  import yaml
33
32
 
34
33
  if os.environ.get('PYWEBEXEC_LDAP_SERVER'):
@@ -609,18 +608,6 @@ if args.cert:
609
608
  app.config['SESSION_COOKIE_SECURE'] = True
610
609
  app.config['TITLE'] = f"{args.title} API"
611
610
 
612
- # Register Swagger UI blueprint with safe token included in API_URL
613
- SWAGGER_URL = '/v0/documentation'
614
- API_URL = '/swagger.yaml'
615
- swaggerui_blueprint = get_swaggerui_blueprint(
616
- SWAGGER_URL,
617
- API_URL,
618
- config={
619
- 'app_name': f"{args.title} API",
620
- 'layout': 'BaseLayout'
621
- }
622
- )
623
- app.register_blueprint(swaggerui_blueprint, url_prefix=SWAGGER_URL)
624
611
 
625
612
  def log_info(fromip, user, message):
626
613
  app.logger.info(f"{user} {fromip}: {message}")
@@ -654,6 +641,7 @@ def get_executables():
654
641
  executables_list.append(exe)
655
642
  return sorted(executables_list, key=lambda x: x["command"])
656
643
 
644
+
657
645
  @app.route('/commands/<command_id>/stop', methods=['PATCH'])
658
646
  def stop_command(command_id):
659
647
  log_request(f"stop_command {command_id}")
@@ -785,9 +773,69 @@ def list_executables():
785
773
  return jsonify({"commands": executables_list})
786
774
 
787
775
 
776
+
788
777
  @app.route('/commands/<cmd>', methods=['POST'])
789
778
  def run_dynamic_command(cmd):
790
779
  # Validate that 'cmd' is an executable in the current directory
780
+
781
+ def parse_params(cmd, data_params):
782
+ if not data_params:
783
+ return []
784
+ # Convert received parameters to exec args.
785
+ # If schema defined for each para, value in data:
786
+ # - if value is True → --<key>
787
+ # - if value is a string → --<param> value
788
+ # - if value is an array → --<param> value1 value2 ...
789
+ # - if value is False → "" (omit)
790
+ # schema_options:
791
+ # separator: " " (default) or "=" is the separator between --param and value
792
+ # noprefix_params: ["param1", "param2"] or ["*"] to omit --param prefix
793
+ # convert_params: {"param1": "param2"} to convert param1 to param2
794
+ exe = get_executable(cmd)
795
+ separator = exe.get("schema", {}).get("schema_options", {}).get("separator", " ")
796
+ noprefix = exe.get("schema", {}).get("schema_options", {}).get("noprefix_params", {})
797
+ convert_params = exe.get("schema", {}).get("schema_options", {}).get("convert_params", {})
798
+ schema_params = exe.get("schema", {}).get("properties", {})
799
+ if isinstance(data_params, dict) and not schema_params:
800
+ return None
801
+ if isinstance(data_params, dict):
802
+ params = ""
803
+ for param in schema_params.keys():
804
+ if not param in data_params:
805
+ continue
806
+ value = data_params[param]
807
+ if value is None:
808
+ continue
809
+ prefix = ""
810
+ if param in convert_params:
811
+ param = convert_params[param]
812
+ prefix = param
813
+ if param in ['--', '', None]:
814
+ separator = ' '
815
+ elif "*" in noprefix or param in noprefix:
816
+ separator = ""
817
+ else:
818
+ prefix = f"--{param}"
819
+ if isinstance(value, bool):
820
+ if value:
821
+ params += f"{prefix} "
822
+ continue
823
+ params += f"{prefix}{separator}"
824
+ if isinstance(value, list):
825
+ params += " ".join(value)
826
+ else:
827
+ params += str(value)
828
+ params += " "
829
+ else:
830
+ params = data_params
831
+ if isinstance(params, str):
832
+ params = [params]
833
+ try:
834
+ params = shlex.split(' '.join(params)) if isinstance(params, list) else []
835
+ except Exception as e:
836
+ return None
837
+ return params
838
+
791
839
  cmd_path = os.path.join(".", os.path.basename(cmd))
792
840
  if not os.path.isfile(cmd_path) or not os.access(cmd_path, os.X_OK):
793
841
  return jsonify({'error': 'Command not found or not executable'}), 400
@@ -795,54 +843,9 @@ def run_dynamic_command(cmd):
795
843
  data = request.json
796
844
  except Exception as e:
797
845
  data = {}
798
-
799
- # Convert received parameters to exec args.
800
- # If schema defined for each para, value in data:
801
- # - if value is True → --<key>
802
- # - if value is a string → --<param> value
803
- # - if value is an array → --<param> value1 value2 ...
804
- # - if value is False → "" (omit)
805
- # schema_options:
806
- # separator: " " (default) or "=" is the separator between --param and value
807
- # noprefix_params: ["param1", "param2"] or ["*"] to omit --param prefix
808
- # convert_params: {"param1": "param2"} to convert param1 to param2
809
- exe = get_executable(cmd)
810
- separator = exe.get("schema", {}).get("schema_options", {}).get("separator", " ")
811
- noprefix = exe.get("schema", {}).get("schema_options", {}).get("noprefix_params", {})
812
- convert_params = exe.get("schema", {}).get("schema_options", {}).get("convert_params", {})
813
- if data.get('params') and isinstance(data['params'], dict):
814
- params = ""
815
- for param, value in data['params'].items():
816
- if value is None:
817
- continue
818
- prefix = ""
819
- if param in convert_params:
820
- param = convert_params[param]
821
- prefix = param
822
- if param in ['--', '', None]:
823
- separator = ' '
824
- elif "*" in noprefix or param in noprefix:
825
- separator = ""
826
- else:
827
- prefix = f"--{param}"
828
- if isinstance(value, bool):
829
- if value:
830
- params += f"{prefix} "
831
- continue
832
- params += f"{prefix}{separator}"
833
- if isinstance(value, list):
834
- params += " ".join(value)
835
- else:
836
- params += str(value)
837
- params += " "
838
- else:
839
- params = data.get("params", [])
840
- if isinstance(params, str):
841
- params = [params]
842
- try:
843
- params = shlex.split(' '.join(params)) if isinstance(params, list) else []
844
- except Exception as e:
845
- return jsonify({'error': str(e)}), 400
846
+ params = parse_params(cmd, data.get('params'))
847
+ if params is None:
848
+ return jsonify({'error': 'Invalid parameters'}), 400
846
849
  rows = data.get('rows', tty_rows) or tty_rows
847
850
  cols = data.get('cols', tty_cols) or tty_cols
848
851
  user = session.get('username', '-')
@@ -870,6 +873,10 @@ def get_command_status(command_id):
870
873
  def index():
871
874
  return render_template('index.html', title=args.title)
872
875
 
876
+ @app.route('/v0/documentation/')
877
+ def swagger_ui():
878
+ return render_template('swagger_ui.html', title=app.config.get('TITLE', 'PyWebExec API'))
879
+
873
880
  @app.route('/commands', methods=['GET'])
874
881
  def list_commands():
875
882
  commands = read_commands()
@@ -982,8 +989,8 @@ def swagger_yaml():
982
989
  "type": "object",
983
990
  "properties": {
984
991
  "params": {"type": "array", "items": {"type": "string"}, "default": []},
985
- "rows": {"type": "integer", "default": tty_rows},
986
- "cols": {"type": "integer", "default": tty_cols}
992
+ "rows": {"type": "integer", "description": "tty nb rows", "default": tty_rows},
993
+ "cols": {"type": "integer", "description": "tty nb columns", "default": tty_cols}
987
994
  }
988
995
  }
989
996
  if exe["schema"]:
@@ -1019,7 +1026,7 @@ def swagger_yaml():
1019
1026
  }
1020
1027
  }
1021
1028
  swagger_spec['info']['title'] = app.config.get('TITLE', 'PyWebExec API')
1022
- swagger_spec_str = yaml.dump(swagger_spec)
1029
+ swagger_spec_str = yaml.dump(swagger_spec, sort_keys=False)
1023
1030
  return Response(swagger_spec_str, mimetype='application/yaml')
1024
1031
  except Exception as e:
1025
1032
  return Response(f"Error reading swagger spec: {e}", status=500)