def run(self, params={}): domains = params.get("domains") if len(domains) == 1: domains = str(domains[0]) try: remoteCategories = self.connection.investigate.categorization( domains, labels=True) except Exception as e: raise PluginException(preset=PluginException.Preset.UNKNOWN, data=e) categories = [] for key, value in remoteCategories.items(): categories.append({ "name": key, "status": value.get("status"), "security_categories": value.get("security_categories"), "content_categories": value.get("content_categories"), }) return {"categories": categories}
def run(self, params={}): """Run action""" opts = params.get(Input.OPTIONS) or {} push_info = opts.get("pushinfo") if push_info: push_info = urllib.parse.urlencode(push_info) user_id = None if params.get(Input.USER_ID): user_id = params.get(Input.USER_ID) username = None if params.get(Input.USERNAME): username = params.get(Input.USERNAME) if (username and user_id) or (user_id is None and username is None): raise PluginException( cause="Wrong input", assistance="Only user_id or username should be used. Not both." ) response = self.connection.auth_api.auth( factor=params[Input.FACTOR], username=username, user_id=user_id, ipaddr=params.get(Input.IPADDR), async_txn=params.get(Input.ASYNC), type=opts.get("type"), display_username=username, pushinfo=push_info, device=params.get(Input.DEVICE), passcode=opts.get("passcode"), ) return response
def extract_json_data(response: requests.Response) -> Dict: try: dct = response.json() except json.decoder.JSONDecodeError: raise PluginException(preset=PluginException.Preset.INVALID_JSON, data=response.text) return dct
def dn_escape_and_split(dn): """ This method will split a dn into it's component peaces and then escape the needed characters :param dn: :return: Will return a list of the dn component peaces """ dn_list = re.split(r',..=', dn) attribute = re.findall(r',..=', dn) # Ensure that special characters are escaped character_list = ['\\', ',', '#', '+', '<', '>', ';', '"', '/'] for idx, value in enumerate(dn_list): for escaped_char in character_list: if f'\{escaped_char}' not in value: dn_list[idx] = dn_list[idx].replace( escaped_char, f'\{escaped_char}') # Re add the removed ,..= to temp list strings then remove the unneeded comma try: for idx, value in enumerate(attribute): dn_list[idx + 1] = f'{value}{dn_list[idx+1]}'[1:] except PluginException as e: raise PluginException( cause='The input DN was invalid. ', assistance='Please double check input. Input was:{dn}') from e return dn_list
def run(self, params={}): try: self.connection.bucket_session.headers.update( {"Content-Type": "application/json"}) user = self.connection.bucket_session.get( f"{self.connection.base_api}/users/{params.get(Input.USERNAME)}" ) user_obj = user.json() if user.status_code == 200: user_info = helpers.clean_json({ "username": user_obj["username"] if "username" in user_obj else params.get(Input.USERNAME), "url": "https://bitbucket.org/" + params.get(Input.USERNAME), "display name": user_obj["display_name"] if "display_name" in user_obj else "", "website": user_obj["website"] if "website" in user_obj else "", "location": user_obj["location"] if "location" in user_obj else "", }) return {Output.USER: user_info} return {"error": user_obj["error"]["message"]} except requests.exceptions.RequestException as e: raise PluginException(cause="User repository error", data=e)
def run(self, params={}): policy_name = params.get(Input.ACCESS_POLICY) rule_name = params.get(Input.RULE_NAME) with fmcapi.FMC(host=self.connection.host, username=self.connection.username, password=self.connection.password, autodeploy=True, limit=10) as fmc1: urls = {'objects': []} for url in params.get(Input.URL_OBJECTS): if len(url['url']) > 400: raise PluginException( cause='URL exceeds max length of 400.', assistance='Please shorten the URL or try another.') url_id = self.make_url_object(fmc=fmc1, name=url['name'], fqdn=url['url']) urls['objects'].append({ 'type': 'URL', 'id': url_id, 'name': url['name'] }) policy = self.make_policy(fmc=fmc1, policy_name=policy_name) acr_id = self.make_rule(fmc=fmc1, policy=policy, rule_name=rule_name, urls=urls) return {Output.SUCCESS: True}
def op(self, cmd: str) -> dict: querystring = {"type": "op", "key": self.key, "cmd": cmd} response = self.session.get(self.url, params=querystring, verify=self.verify_cert) try: output = xmltodict.parse(response.text) except TypeError: raise ServerException( cause='The response from PAN-OS was not the correct data type.', assistance='Contact support for help.', data=response.text) except SyntaxError: raise ServerException( cause='The response from PAN-OS was malformed.', assistance='Contact support for help.', data=response.text) except BaseException as e: raise PluginException( cause= 'An unknown error occurred when parsing the PAN-OS response.', assistance='Contact support for help.', data=f'{response.text}, error {e}') if output['response']['@status'] == 'error': error = output['response']['msg'] error = json.dumps(error) raise ServerException( cause='PAN-OS returned an error in response to the request.', assistance= 'Double that check inputs are valid. Contact support if this issue persists.', data=error) return output
def run(self, params={}): # Generate unique identifier for report names identifier = uuid.uuid4() scan_id = params.get(Input.SCAN_ID) # Report to collect site ID and asset IDs of scan report_payload = { 'name': f"Rapid7-ScanAssets-InsightConnect-{identifier}", 'format': 'sql-query', 'query': 'SELECT site_id, asset_id ' 'FROM dim_site_scan AS dss ' 'JOIN dim_asset_scan AS das ON das.scan_id = dss.scan_id', 'version': '2.3.0', 'scope': {'scan': scan_id} } report_contents = util.adhoc_sql_report(self.connection, self.logger, report_payload) self.logger.info(f"Processing Assets of Scan ID {scan_id}") # Extract site ID and asset IDs scan_asset_ids = set() scan_site_id = None try: csv_report = csv.DictReader(io.StringIO(report_contents['raw'])) except Exception as e: raise PluginException(cause=f"Error: Failed to process query response for assets returned for " f"scan ID {scan_id}.", assistance=f"Exception returned was {e}") for row in csv_report: scan_asset_ids.add(int(row["asset_id"])) # Assign site ID for scan if scan_site_id is None: scan_site_id = row["site_id"] # Get assets of site of scan resource_helper = ResourceRequests(self.connection.session, self.logger) search_criteria = { "filters": [ { "field": "site-id", "operator": "in", "values": [scan_site_id] } ], "match": "all" } self.logger.info("Performing filtered asset search with criteria %s" % search_criteria) endpoint = endpoints.Asset.search(self.connection.console_url) site_assets = resource_helper.paged_resource_request(endpoint=endpoint, method='post', payload=search_criteria) # Filter assets to specific scan assets filtered_assets = [asset for asset in site_assets if asset["id"] in scan_asset_ids] return {Output.ASSETS: filtered_assets}
def get_users_in_group(self, api_url): returned_data = [] response = self.connection.session.get(api_url) next_link = None links = response.headers.get("Link", "").split(", ") for link in links: if 'rel="next"' in link: matched_link = re.match("<(.*?)>", link) if matched_link: next_link = matched_link.group(1) if next_link: returned_data.extend(self.get_users_in_group(next_link)) try: data = response.json() except ValueError: raise PluginException( cause="Returned data was not in JSON format.", assistance="Double-check that group ID's are all valid.", data=response.text, ) helpers.raise_based_on_error_code(response) returned_data.extend(komand.helper.clean(data)) return returned_data
def run(self, params={}): """Run the trigger""" self.jql = params.get(Input.JQL) or '' self.get_attachments = params.get(Input.GET_ATTACHMENTS, False) self.project = params.get(Input.PROJECT) valid_project = look_up_project(self.project, self.connection.client) if not valid_project: raise PluginException( cause=f"Project {self.project} does not exist or the user does not have permission to access the project.", assistance='Please provide a valid project ID/name or make sure the project is accessible to the user.') if self.project: if self.jql: self.jql = 'project=' + self.project + ' and ' + self.jql else: self.jql = 'project=' + self.project self.logger.info('Querying %s', self.jql) self.initialize() while True: self.poll() time.sleep(params.get(Input.POLL_TIMEOUT, 60))
def run(self, params={}): try: domain = params.get(Input.DOMAIN).strip('https://').split('/')[0] comment = params.get(Input.COMMENT) fields = [ "analystNotes", "counts", "enterpriseLists", "entity", "intelCard", "metrics", "relatedEntities", "risk", "sightings", "threatLists", "timestamps" ] if not comment: comment = None domain_report = self.connection.client.lookup_domain(domain, fields=fields, comment=comment) if domain_report.get("warnings", False): self.logger.warning(f"Warning: {domain_report.get('warnings')}") return komand.helper.clean(domain_report["data"]) except Exception as e: PluginException(cause=f"Error: {e}", assistance="Review exception")
def _create_user(self, params, passwd): account_enabled = params.get(Input.ACCOUNT_ENABLED, True) display_name = params.get(Input.DISPLAY_NAME) mail_nickname = params.get(Input.MAIL_NICKNAME, display_name) user_principal_name = params.get(Input.USER_PRINCIPAL_NAME) user = { "accountEnabled": account_enabled, "displayName": display_name, "mailNickname": mail_nickname, "userPrincipalName": user_principal_name, "passwordProfile": { "forceChangePasswordNextSignIn": True, "password": passwd }, } headers = self.connection.get_headers(self.connection.get_auth_token()) endpoint_formatted = f"https://graph.microsoft.com/v1.0/{self.connection.tenant}/users/" result = requests.post(endpoint_formatted, headers=headers, json=user) try: result.raise_for_status() except Exception as e: raise PluginException(preset=PluginException.Preset.UNKNOWN, data=result.text) from e # 201 Created is the response according to MS. # https://docs.microsoft.com/en-us/graph/api/user-post-users?view=graph-rest-1.0&tabs=http#response-1 return result.status_code == 201
def run(self, params={}): with tempfile.TemporaryDirectory() as tempdir: chrome_options = Options() chrome_options.add_argument("--headless") chrome_options.add_argument("--window-size=%s" % self.WINDOW_SIZE) chrome_options.add_argument("--no-sandbox") chrome_options.add_argument("--disable-dev-shm-usage") chrome_options.add_argument("--disable-gpu") chrome_options.add_argument(f"--user-data-dir={tempdir}") chrome_options.binary_location = self.CHROME_PATH url = params.get(Input.URL, "") delay = params.get(Input.DELAY, 0) if not url.lower().startswith('http'): raise PluginException( cause="Input Error:", assistance="URLs need to start with 'http'") driver = webdriver.Chrome(executable_path=self.CHROMEDRIVER_PATH, options=chrome_options) driver.get(url) time.sleep(delay) full_page = params.get(Input.FULL_PAGE, False) if full_page: try: body = driver.find_element_by_tag_name('body') img_str = body.screenshot_as_base64 except Exception: img_str = self.screenshot_to_file(driver) else: img_str = self.screenshot_to_file(driver) driver.quit() return {Output.SCREENSHOT: img_str}
def run(self, params={}): """TODO: Run action""" client = self.connection.client _file = io.BytesIO(base64.b64decode(params.get('file'))) filename = params.get('filename') out = {} try: if filename: self.logger.info('Filename specified: %s', filename) out = client.submit_file(_file, filename) else: out = client.submit_file(_file) out['supported_file_type'] = True except pyldfire.WildFireException as e: if e.args and "Unsupport File type" in e.args[ 0]: # Yes, that's the error, not a typo out['supported_file_type'] = False else: raise PluginException(PluginException.Preset.UNKNOWN) from e if 'filename' not in out.keys(): out['filename'] = 'Unknown' if 'url' not in out.keys(): out['url'] = 'Unknown' return {'submission': komand.helper.clean(out)}
def http_errors(self, response: dict, status_code: int) -> None: """ Will look for and handle non 2XX status codes :param response: The JSON response from the API call :param status_code: The API calls status code """ if status_code in self._STATUS_CODES: raise PluginException( cause=self._STATUS_CODES[status_code]["cause"], assistance=self._STATUS_CODES[status_code]["assistance"], data=f"Raw response data: {response}") if status_code not in range(200, 299): raise PluginException( cause="An undocumented response code was returned.", assistance="Contact support for assistance", data=f"Raw response data: {response}")
def run(self, params={}): group_ids = params.get(Input.GROUP_ID) user_id = params.get(Input.USER_ID) self.logger.info(f"Getting user info: {user_id}") user_response = get_user_info(self.connection, user_id, self.logger) user_object = user_response.json() user = { "@odata.id": f"https://graph.microsoft.com/v1.0/{self.connection.tenant}/users/{user_object.get('id')}" } headers = self.connection.get_headers(self.connection.get_auth_token()) for group_id in group_ids: add_to_group_endpoint = f"https://graph.microsoft.com/v1.0/{self.connection.tenant}/groups/{group_id}/members/$ref" result = requests.post(add_to_group_endpoint, json=user, headers=headers) if not result.status_code == 204: raise PluginException( cause= f"Add User to Group call returned an unexpected response: {result.status_code}", assistance= f"Check that the group id {group_id} and user id {user_id} are correct.", data=result.text) return {Output.SUCCESS: True}
def resource_request(self, endpoint: str, method: str = "get", params: dict = None, payload: dict = None) -> dict: """ Sends a request to API with the provided endpoint and optional method/payload :param endpoint: Endpoint for the API call defined in endpoints.py :param method: HTTP method for the API request :param params: URL parameters to append to the request :param payload: JSON body for the API request if required :return: Dict containing the JSON response body """ try: request_method = getattr(self.session, method.lower()) if not params: params = {} if not payload: response = request_method(url=endpoint, params=params, verify=False) else: response = request_method(url=endpoint, params=params, json=payload, verify=False) except requests.RequestException as e: self.logger.error(e) raise if response.status_code in range(200, 299): resource = response.text return {"resource": resource, "status": response.status_code} else: try: error = response.json()["message"] except KeyError: self.logger.error(f"Code: {response.status_code}, message: {error}") error = "Unknown error occurred. Please contact support or try again later." status_code_message = self._ERRORS.get(response.status_code, self._ERRORS[000]) self.logger.error(f"{status_code_message} ({response.status_code}): {error}") raise PluginException(f"InsightIDR returned a status code of {response.status_code}: {status_code_message}")
def run(self, params={}): add = util.ExternalList() name = params.get("name") list_type = params.get("list_type") description = params.get("description") source = params.get("source") repeat = params.get("repeat") time = params.get("time") day = params.get("day") xpath = "/config/devices/entry/vsys/entry/external-list/entry[@name='{}']".format( name) element = add.element_for_create_external_list( self._LIST_TYPE_KEY[list_type], description, source, self._REPEAT_KEY[repeat], time, day.lower, ) output = self.connection.request.set_(xpath=xpath, element=element) try: status = output["response"]["@status"] code = output["response"]["@code"] message = output["response"]["msg"] return {"status": status, "code": code, "message": message} except KeyError: raise PluginException( cause="The output did not contain expected keys.", assistance="Contact support for help.", data=output, )
def run(self, params={}): query = params.get(Input.QUERY) log_set_name = params.get(Input.LOG_SET) timeout = params.get(Input.TIMEOUT) time_from_string = params.get(Input.TIME_FROM) relative_time_from = params.get(Input.RELATIVE_TIME) time_to_string = params.get(Input.TIME_TO) # Time To is optional, if not specified, time to is set to now time_from, time_to = parse_dates(time_from_string, time_to_string, relative_time_from) if time_from > time_to: raise PluginException( cause="Time To input was chronologically behind Time From.", assistance="Please edit the step so Time From is chronologically behind (in the past) relative to Time To.\n", data=f"\nTime From: {time_from}\nTime To:{time_to}", ) log_set_id = self.get_log_set_id(log_set_name) # The IDR API will SOMETIMES return results immediately. # It will return results if it gets them. If not, we'll get a call back URL to work on callback_url, log_entries = self.maybe_get_log_entries(log_set_id, query, time_from, time_to) if not log_entries: log_entries = self.get_results_from_callback(callback_url, timeout) log_entries = komand.helper.clean(log_entries) for log_entry in log_entries: log_entry["message"] = json.loads(log_entry.get("message", "{}")) self.logger.info(f"Sending results to orchestrator.") return {Output.RESULTS: log_entries, Output.COUNT: len(log_entries)}
def run(self, params={}): """Run the trigger""" # send a test event self.jql = params.get('jql') or '' self.get_attachments = params.get('get_attachments', False) self.project = params.get('project') # Check if project exists valid_project = look_up_project(self.project, self.connection.client) if not valid_project: raise PluginException( cause= f"Project {self.project} does not exist or user don't have permission to access the project.", assistance= 'Please provide a valid project ID/name or make sure project is accessible to user.' ) if self.project: if self.jql: self.jql = 'project=' + self.project + ' and ' + self.jql else: self.jql = 'project=' + self.project self.logger.info('Querying %s', self.jql) self.initialize() while True: self.poll() time.sleep(60)
def host_formatter(self, host: str) -> str: """ Formats The host as needed for the connection """ colons = host.count(":") if colons > 0: host = host.split(":") if colons == 1: if host[1].find("//") != -1: host = host[1][2:] else: self.logger.info( "Port was provided in hostname, using value from Port field instead" ) host = host[0] elif colons == 2: self.logger.info( "Port was provided in hostname, using value from Port field instead" ) host = host[1] if host.find("//") != -1: host = host[2:] else: raise PluginException( cause= f"There are too many colons ({colons}) in the host name ({host}).", assistance="Check that the host name is correct", data=host, ) backslash = host.find("/") if backslash != -1: host = host[:backslash] return host
def run(self, params={}): policy_name = params.get(Input.ACCESS_POLICY) rule_name = params.get(Input.RULE_NAME) with fmcapi.FMC( host=self.connection.host, username=self.connection.username, password=self.connection.password, autodeploy=True, limit=10, ) as fmc1: urls = {"objects": []} for url in params.get(Input.URL_OBJECTS): if len(url["url"]) > 400: raise PluginException( cause="URL exceeds max length of 400.", assistance="Please shorten the URL or try another.", ) url_id = self.make_url_object(fmc=fmc1, name=url["name"], fqdn=url["url"]) urls["objects"].append({"type": "URL", "id": url_id, "name": url["name"]}) policy = self.make_policy(fmc=fmc1, policy_name=policy_name) acr_id = self.make_rule(fmc=fmc1, policy=policy, rule_name=rule_name, urls=urls) return {Output.SUCCESS: True}
def run(self, params={}): headers = {'Content-Type': 'application/json'} params['token'] = self.connection.token priorities = { 'Lowest': -2, 'Low': -1, 'Normal': 0, 'High': 1, 'Emergency': 2 } params['priority'] = priorities[params.get('priority','Normal')] if(params.get('priority', 0) == 2): if(params.get('retry', 0) < 30): self.logger.info("SendMessage: retry interval too short - setting to 30 seconds") params['retry'] = 30 if(params.get('expire', 0) == 0): self.logger.info("SendMessage: emergency priority set but expiry not defined, setting expiry to 1 hour") params['expiry'] = 3600 else: params.pop('retry', None) params.pop('expire', None) if(params.get('timestamp', '000').startswith("000")): params.pop('timestamp', None) res = requests.post(self.connection.api_url, data=json.dumps(params), headers=headers) if(res.status_code != 200): raise PluginException(preset=PluginException.Preset.UNKNOWN) try: return res.json() except json.decoder.JSONDecodeError: raise ConnectionTestException(preset=PluginException.Preset.INVALID_JSON, data=res.text)
def run(self, params={}): url = f"https://{self.connection.server_ip}:{self.connection.server_port}/web_api/show-access-rulebase" headers = self.connection.get_headers() payload = { "offset": 0, "limit": params.get(Input.LIMIT, 1), "name": params.get(Input.LAYER_NAME), "details-level": "full", "use-object-dictionary": True } result = requests.post(url, headers=headers, json=payload, verify=self.connection.ssl_verify) try: result.raise_for_status() except Exception as e: raise PluginException( cause=f"Show Access Rules from {url} failed.\n", assistance=f"{result.text}\n", data=e) return {Output.ACCESS_RULES: komand.helper.clean(result.json())}
def run(self, params={}): payload_notes = '' user_notes = params.get(Input.NOTES) if user_notes: payload_notes = user_notes payload_scan_action = params.get(Input.SCAN_ACTION) payload_filename = params.get(Input.FILE).get('filename') payload_file_contents = params.get(Input.FILE).get('content') payload = { "file_name": payload_filename, "file_content_base64_string": payload_file_contents, "file_scan_action": payload_scan_action, "note": payload_notes } json_payload = json.dumps(payload) self.connection.create_jwt_token(self.api_path, self.api_http_method, json_payload) request_url = self.connection.url + self.api_path response = None try: response = requests.put(request_url, headers=self.connection.header_dict, data=json_payload, verify=False) response.raise_for_status() return {Output.SUCCESS: response is not None} except RequestException as rex: if response: self.logger.error(f"Received status code: {response.status_code}") self.logger.error(f"Response was: {response.text}") raise PluginException(assistance="Please verify the connection details and input data.", cause=f"Error processing the Apex request: {rex}") return {Output.SUCCESS : False}
def run(self, params={}): table = "sys_journal_field" url = f'{self.connection.table_url}{table}' system_id = params.get(Input.SYSTEM_ID) type_ = "work_notes" if params.get(Input.TYPE) == "work notes" else params.get(Input.TYPE) fields = "sys_id,sys_created_on,name,element_id,sys_tags,value,sys_created_by,element" if type_ == "all": url = f'{url}?sysparm_query=element_id={system_id}&sysparm_fields={fields}' elif type_ == "comments": url = f'{url}?sysparm_query=element_id={system_id}^element={type_}&sysparm_fields={fields}' elif type_ == "work_notes": url = f'{url}?sysparm_query=element_id={system_id}^element={type_}&sysparm_fields={fields}' method = "get" response = self.connection.request.make_request(url, method) try: result = response["resource"].get("result") except KeyError as e: raise PluginException(preset=PluginException.Preset.UNKNOWN, data=response.text) from e return { Output.INCIDENT_COMMENTS_WORKNOTES: result }
def search(self, pattern, start=None, limit=None, include_category=None): '''Searches for domains that match a given pattern''' params = dict() if start is None: start = datetime.timedelta(days=30) if isinstance(start, datetime.timedelta): params['start'] = int( time.mktime( (datetime.datetime.utcnow() - start).timetuple()) * 1000) elif isinstance(start, datetime.datetime): params['start'] = int(time.mktime(start.timetuple()) * 1000) else: raise PluginException( cause='Unable to retrieve domains for search.', assistance=Investigate.SEARCH_ERR) if limit is not None and isinstance(limit, int): params['limit'] = limit if include_category is not None and isinstance(include_category, bool): params['includeCategory'] = str(include_category).lower() uri = self._uris['search'].format(urllib.parse.quote_plus(pattern)) return self.get_parse(uri, params)
def run(self, params={}): min_time = params.get(Input.MINTIME, 1000000000000) auth_logs = [] try: results = self.connection.admin_api.get_authentication_log( api_version=2, mintime=min_time, limit=str(self.PAGE_SIZE) ) auth_logs.extend(results[self.AUTH_LOGS]) total_objects_left = results[self.METADATA][self.TOTAL_OBJECTS] - self.PAGE_SIZE next_offset = results[self.METADATA][self.NEXT_OFFSET] while total_objects_left > 0: results = self.connection.admin_api.get_authentication_log( api_version=2, mintime=min_time, limit=str(self.PAGE_SIZE), next_offset=next_offset ) next_offset = results[self.METADATA][self.NEXT_OFFSET] total_objects_left -= self.PAGE_SIZE auth_logs.extend(results[self.AUTH_LOGS]) auth_logs = komand.helper.clean(auth_logs) return {Output.AUTHLOGS: auth_logs} except KeyError as e: raise PluginException( preset=PluginException.Preset.SERVER_ERROR, data=f"Error: API response was missing required fields necessary for proper functioning. {str(e)}" )
def make_request(self, endpoint, method, payload=None, params=None, content_type="application/json"): try: request_method = getattr(self.session, method.lower()) headers = { "Content-Type": content_type, "Accept": "application/json" } if not params: params = {} response = request_method(url=endpoint, headers=headers, params=params, json=payload, verify=False) except requests.RequestException as e: self.logger.error(e) raise if response.status_code in range(200, 299): try: resource = None if response.status_code == 204 else response.json( ) except json.decoder.JSONDecodeError: raise PluginException( preset=PluginException.Preset.INVALID_JSON, data=response.text) return {'resource': resource, 'status': response.status_code} else: try: error = response.json() except json.decoder.JSONDecodeError: raise PluginException( preset=PluginException.Preset.INVALID_JSON, data=response.text) raise PluginException( cause=f'Error in API request to ServiceNow. ', assistance= f'Status code: {response.status_code}, Error: {error}')
def run(self, params={}): formatter = ADUtils() conn = self.connection.conn dn = params.get('distinguished_name') group_dn = params.get('group_dn') add_remove = params.get('add_remove') # Normalize dn dn = formatter.format_dn(dn)[0] dn = formatter.unescape_asterisk(dn) self.logger.info(f'Escaped DN {dn}') # Normalize group dn group_dn = formatter.format_dn(group_dn)[0] group_dn = formatter.unescape_asterisk(group_dn) self.logger.info(f'Escaped group DN {group_dn}') if add_remove == 'add': try: group = extend.ad_add_members_to_groups(conn, dn, group_dn) except LDAPInvalidDnError as e: raise PluginException( cause= 'Either the user or group distinguished name was not found.', assistance= 'Please check that the distinguished names are correct', data=e) else: try: group = extend.ad_remove_members_from_groups(conn, dn, group_dn, fix=True) except LDAPInvalidDnError as e: raise PluginException( cause= 'Either the user or group distinguished name was not found.', assistance= 'Please check that the distinguished names are correct', data=e) if group is False: self.logger.error( 'ModifyGroups: Unexpected result for group. Group was ' + str(group)) raise PluginException(preset=PluginException.Preset.UNKNOWN) return {'success': group}