예제 #1
0
    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
예제 #2
0
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()
예제 #3
0
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
예제 #4
0
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
예제 #5
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
예제 #6
0
 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)