def signIn(self, user: str, pwd: str, refresh_token: bool = False) -> bool: """ Checks if the current session is authenticated. If not, a token is retrieved (if not available yet) and its value is set in the session X-XSRF-TOKEN header. Finally, the user will be signed in to GeoNetwork. :param user: The basic authentication user name. :param pwd: The basic authentication password. :param refresh_token: If True (default = False), the token will be refreshed. :returns: True if sign in was successful. """ if GeonetworkSession.HEADER_TOKEN in self.headers and self.signedIn and not refresh_token: # We are still signed in: no need to do it again return True if not (user and pwd): return False auth = {'username': user, 'password': pwd} if GeonetworkSession.HEADER_TOKEN not in self.headers or refresh_token: # Obtain cookie token if there is none or we want to refresh it token = self.getToken() if token: # Update session headers with an X-XSRF-TOKEN self.headers[GeonetworkSession.HEADER_TOKEN] = token auth['_csrf'] = self.headers[GeonetworkSession.HEADER_TOKEN] headers = {'Accept': 'application/html'} feedback.logInfo(f'POST {self._signin_url}') # Note: it is **extremely** important to NOT allow redirects here! # Disallowing redirects will sign us in successfully and return a 302 (Found). # A redirect however will result in a 403 (Access Denied), even with valid credentials. result = self.post(self._signin_url, data=auth, headers=headers, allow_redirects=False) status = result.status_code if status >= 400: prefix = f"Failed to sign in to {self._signin_url}" if status == 403: if not refresh_token: # Retry 1 more time with a refreshed token feedback.logWarning(f"{prefix}: retrying with new token") return self.signIn(user, pwd, True) feedback.logError( f"{prefix}: access denied to user '{user}' ({status})") elif status == 401: feedback.logError( f"{prefix}: user '{user}' not authorized (bad credentials)" ) else: feedback.logError( f"{prefix}: server returned {status} (please check server logs)" ) return False return True
def removeServer(name, silent: bool = False): """ Removes the server with the given name from the Bridge server settings. :param name: The server name as defined by the user. :param silent: If True (default is False), a warning is logged when the server name does not exist. """ global _instances try: del _instances[name] except KeyError: if not silent: feedback.logWarning(f"Server named '{name}' does not exist or has already been removed") else: saveConfiguredServers()
def saveServer(server, replace_key: str) -> bool: """ Adds the given server (instance) to the Bridge server settings. Returns True if the instance was successfully saved. :param server: The new server instance to store. :param replace_key: The key (server name) under which to save the server instance. If the key matches the `server.serverName`, the server is updated under that key. If it doesn't match, the server instance under the given key is deleted and a new instance is saved under a new key equal to the `server.serverName`. :returns: True if save was successful. :raises: ValueError if `replace_key` does not match the `server.serverName` and the new `server.serverName` already exists in the `_instances` dictionary (duplicate key), or if `server.serverName` is empty. """ global _instances if not isinstance(server, (bases.ServerBase, bases.CombiServerBase)): # This should not happen if the server properly implements (Combi)ServerBase feedback.logError(f"Cannot store server of type '{server.__class__.__name__}': " f"must implement {bases.ServerBase.__name__} or {bases.CombiServerBase.__name__}") return False if not server.serverName: # Make sure that the server has a non-empty name raise ValueError('server name cannot be empty') if replace_key != server.serverName: # User has renamed the server: check if new name does not exist yet if server.serverName in getServerNames(): raise ValueError(f"server named '{server.serverName}' already exists") try: # Remove instance under old name del _instances[replace_key] except KeyError: feedback.logWarning(f"server named '{replace_key}' does not exist") # Save instance using (new) server name as key _instances[server.serverName] = server if isinstance(server, bases.CatalogServerBase): server.addOGCServices() if not saveConfiguredServers(): # Remove server again if the instance could not be saved in QGIS settings del _instances[server.serverName] return False return True
def deserializeServers(config_str: str) -> bool: """ Deserializes a JSON server configuration string and creates Bridge server instances. :param config_str: A JSON string. Must be deserializable as a list of (str, dict). :returns: True when successful, False otherwise. """ global _instances # Parse JSON object from settings string try: stored_servers = json.loads(config_str) except json.JSONDecodeError as e: feedback.logError(f"Failed to parse servers configuration: {e}") return False # It is expected that `stored_servers` is a list if not isinstance(stored_servers, list) or len(stored_servers) == 0: feedback.logError("Server configuration must be a non-empty list") return False # Instantiate servers from models and settings num_added = 0 for (server_type, properties) in stored_servers: name = properties.get('name') if not name: feedback.logWarning(f'Skipped {server_type} entry due to missing name') continue key = getUniqueName(name) if key != name: properties['name'] = key try: s = _initServer(server_type, **properties) except (UnknownServerError, ServerInitError) as e: feedback.logError(f"Failed to load {server_type} type: {e}") continue if key != name: feedback.logWarning(f"Changed name from '{name}' to '{key}' for " f"non-unique {s.getLabel()} entry") _instances[key] = s num_added += 1 # Return True if any of the servers initialized successfully return num_added > 0
def getModelLookup(force: bool = False) -> dict: """ Load all supported server class models from the `servers.models` folder and returns a lookup dict. The dictionary has the model names as keys and the actual types/classes as values. :param force: When True (default is False), all server models will be reloaded. """ global _types if force: # Reset the available server types _types = {} if _types: # Do nothing if the server types were already loaded return _types package_dir = Path(models.__file__).resolve().parent for (_, module_name, _) in iter_modules([package_dir]): module = import_module(f"{models.__name__}.{module_name}") # Iterate all non-imported classes in the module for name, cls in ( (k, v) for k, v in module.__dict__.items() if isclass(v) and v.__module__ == module.__name__): # noqa # Server class must inherit from ServerBase or CombiServerBase if not issubclass(cls, (bases.ServerBase, bases.CombiServerBase)): continue # Concrete server classes that do NOT implement the AbstractServer methods # will raise a TypeError once the class is being initialized. # However, that error message is rather obscure, so we will check beforehand # if the methods have been implemented and log warnings. add_type = True for method in getattr(bases.AbstractServer, '__abstractmethods__'): if hasattr(getattr(cls, method, None), '__isabstractmethod__'): feedback.logWarning( f"{name} class does not implement {method}() method") add_type = False # Add type to dictionary if tests passed if add_type: _types[name] = cls return _types
def plugin_hook(t, value, tb): """ Exception handling (catch all) """ error_list = traceback.format_exception(t, value, tb) trace = "".join(error_list) if meta.PLUGIN_NAMESPACE in trace.lower(): try: # Show error report dialog handleError(error_list) except Exception as err: # Swallow all exceptions here, to avoid entering an endless loop feedback.logWarning( f"A failure occurred while handling an exception: {err}" ) # TODO: Once Bridge is more mature, the code below should be uncommented # try: # # Close/disable the plugin to avoid messing up things # self.unload() # except Exception as err: # feedback.logWarning(f"A failure occurred while unloading the Bridge plugin: {err}") else: # Handle regular QGIS exception self.qgis_hook(t, value, tb)