コード例 #1
0
def serializeServers() -> Union[str, None]:
    """ Calls `getSettings()` on all initialized serializable servers and returns them as a JSON string. """
    global _instances

    server_config = []
    bad_servers = []

    # Test servers
    for s in _instances.values():
        server_tuple = _getServerAsTuple(s)
        if not server_tuple:
            feedback.logError(f"Server configuration for '{s.serverName}' cannot be saved and will be removed.'")
            bad_servers.append(s.serverName)
            continue
        server_config.append(server_tuple)

    # Remove bad servers (if any)
    while bad_servers:
        del _instances[bad_servers.pop()]

    # Serialize JSON and output string
    try:
        config_str = json.dumps(server_config)
    except TypeError as e:
        feedback.logError(f"Failed to serialize server configuration as JSON: {e}")
        return
    return config_str
コード例 #2
0
 def getToken(self):
     """ Requests a session token using POST and reads its value from the cookie. """
     headers = {'Content-Type': 'application/xml'}
     feedback.logInfo(f'POST {self._token_url}')
     # Note: it is expected that this returns a 403
     self.post(self._token_url, headers=headers)
     token = self.cookies.get(GeonetworkSession.COOKIE_TOKEN)
     if not token:
         feedback.logError(
             f'Did not receive a {GeonetworkSession.COOKIE_TOKEN} cookie!')
     return token
コード例 #3
0
def parseMe(response: requests.Response) -> bool:
    """ Parses the response from a 'me' request to a GeoNetwork API.
    Returns True if the response was authenticated. """
    try:
        xml = ETree.fromstring(response.text)
        authenticated = xml.find('me').get('authenticated').lower()
    except (TypeError, AttributeError, ETree.ParseError):
        feedback.logError(
            f'Received invalid response from {response.url}: {response.text}')
        return False
    return authenticated == 'true'
コード例 #4
0
 def signedIn(self) -> bool:
     """ Returns True if the session is authenticated (i.e. the user has been signed in). """
     headers = {'Accept': 'application/xml'}
     feedback.logInfo(f'GET {self._token_url}')
     result = self.get(self._token_url, headers=headers)
     if result.status_code < 400:
         return parseMe(result)
     # This should not happen (even unauthenticated responses should return a 200), but we have to handle it
     feedback.logError(
         f'Failed to query {self._token_url}: server returned {result.status_code}'
     )
     return False
コード例 #5
0
 def run(self):
     results = []
     for count, item in enumerate(self._items, 1):
         try:
             results.append(self._func(item))
         except Exception as e:
             # Log a QGIS error message (should be thread-safe)
             logError(e)
         self.progress.emit(count)
         if self._stop:
             break
     self.finished.emit(results)
コード例 #6
0
 def loadAlgorithms(self):
     for server_type in getServerTypes():
         algorithm = server_type.getAlgorithmInstance()
         if not algorithm:
             # Server type does not provide processing algorithm
             continue
         if not isinstance(algorithm, BridgeAlgorithm):
             feedback.logError(
                 f"Skipped algorithm returned by {server_type.__name__}: "
                 f"instance does not inherit {BridgeAlgorithm.__name__}")
             continue
         self.addAlgorithm(algorithm)
コード例 #7
0
 def load(self):
     try:
         ProcessingConfig.settingIcons[self.name()] = self.icon()
         ProcessingConfig.addSetting(
             Setting(self.name(), self.BRIDGE_ACTIVE, self.tr('Activate'),
                     False))
         ProcessingConfig.readSettings()
         self.refreshAlgorithms()
     except Exception as err:
         feedback.logError(err)
         return False
     return True
コード例 #8
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
コード例 #9
0
def _getServerAsTuple(server):
    """ Verifies that the given server instance can be recreated using the values from `getSettings()`.
        If this is the case, a tuple of (type, params) will be returned. Otherwise, None is returned.
    """
    try:
        params = server.getSettings() or {}
    except (AttributeError, NotImplementedError):
        # This should not happen if the server properly implements AbstractServer
        feedback.logError(f"Server does not implement {bases.AbstractServer.__name__}.getSettings")
        return

    server_type = server.__class__.__name__
    try:
        # Test that the server class can be instantiated again using the output from getSettings()
        _initServer(server_type, **params)
    except ServerInitError as e:
        # Report error that the server cannot be initialized with the given parameters
        feedback.logError(f"{server_type}.getSettings() returned bad parameters: {e}")
        return
    return server_type, params
コード例 #10
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
コード例 #11
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
コード例 #12
0
    def run(self):
        def publishLayer(lyr, lyr_name):
            fields = None
            if lyr.type() == lyr.VectorLayer:
                fields = [
                    _name
                    for _name, publish in self.field_map[lyr.id()].items()
                    if publish
                ]
            self.geodata_server.publishLayer(lyr, fields)
            if self.metadata_server is not None:
                metadata_uuid = uuidForLayer(lyr)
                md_url = self.metadata_server.metadataUrl(metadata_uuid)
                self.geodata_server.setLayerMetadataLink(lyr_name, md_url)

        try:
            validator = QgsNativeMetadataValidator()

            # FIXME: remove or improve this
            # DONOTALLOW = 0
            ALLOW = 1
            ALLOWONLYDATA = 2

            allow_without_md = ALLOW  # pluginSetting("allowWithoutMetadata")

            if self.geodata_server is not None:
                self.geodata_server.prepareForPublishing(self.only_symbology)

            self.results = {}
            for i, layer_id in enumerate(self.layer_ids):
                if self.isCanceled():
                    return False
                warnings, errors = [], []
                self.setProgress(i * 100 / len(self.layer_ids))
                layer = lyr_utils.getLayerById(layer_id)
                name, safe_name = lyr_utils.getLayerTitleAndName(layer)
                if not lyr_utils.hasValidLayerName(layer):
                    try:
                        warnings.append(
                            f"Layer name '{name}' contains characters that may cause issues"
                        )
                    except UnicodeError:
                        warnings.append(
                            "Layer name contains characters that may cause issues"
                        )
                md_valid, _ = validator.validate(layer.metadata())
                if self.geodata_server is not None:
                    self.geodata_server.resetLogIssues()

                    # Publish style
                    self.stepStarted.emit(layer_id, SYMBOLOGY)
                    try:
                        self.geodata_server.publishStyle(layer)
                    except:
                        errors.append(traceback.format_exc())
                    self.stepFinished.emit(layer_id, SYMBOLOGY)

                    if self.only_symbology:
                        # Skip data publish if "only symbology" was checked
                        self.stepSkipped.emit(layer_id, DATA)
                    else:
                        # Publish data
                        self.stepStarted.emit(layer_id, DATA)
                        try:
                            if md_valid or allow_without_md in (ALLOW,
                                                                ALLOWONLYDATA):
                                publishLayer(layer, safe_name)
                            else:
                                self.stepStarted.emit(layer_id, DATA)
                                if md_valid or allow_without_md in (
                                        ALLOW, ALLOWONLYDATA):
                                    publishLayer(layer, safe_name)
                                else:
                                    self.geodata_server.logError(
                                        f"Layer '{name}' has invalid metadata. "
                                        f"Layer was not published")
                                self.stepFinished.emit(layer_id, DATA)
                        except:
                            errors.append(traceback.format_exc())
                        self.stepFinished.emit(layer_id, DATA)

                else:
                    # No geodata server selected: skip layer data and symbology
                    self.stepSkipped.emit(layer_id, SYMBOLOGY)
                    self.stepSkipped.emit(layer_id, DATA)

                if self.metadata_server is not None:
                    # User selected metadata server: publish metadata
                    try:
                        self.metadata_server.resetLogIssues()
                        if md_valid or allow_without_md == ALLOW:
                            wms = None
                            wfs = None
                            full_name = None
                            if self.geodata_server is not None:
                                full_name = self.geodata_server.fullLayerName(
                                    safe_name)
                                wms = self.geodata_server.getWmsUrl()
                                if layer.type() == layer.VectorLayer:
                                    wfs = self.geodata_server.getWfsUrl()
                            self.autofillMetadata(layer)
                            self.stepStarted.emit(layer_id, METADATA)
                            self.metadata_server.publishLayerMetadata(
                                layer, wms, wfs, full_name)
                            self.stepFinished.emit(layer_id, METADATA)
                        else:
                            self.metadata_server.logError(
                                f"Layer '{name}' has invalid metadata. "
                                f"Metadata was not published")
                    except:
                        errors.append(traceback.format_exc())
                else:
                    self.stepSkipped.emit(layer_id, METADATA)

                # Collect all layer-specific errors and warnings (if any)
                if self.geodata_server is not None:
                    w, e = self.geodata_server.getLogIssues()
                    warnings.extend(w)
                    errors.extend(e)
                if self.metadata_server is not None:
                    w, e = self.metadata_server.getLogIssues()
                    warnings.extend(w)
                    errors.extend(e)
                self.results[name] = (set(warnings), set(errors))

            # Create layer groups (if any)
            if self.geodata_server is not None:
                self.stepStarted.emit(None, GROUPS)
                try:
                    # FIXME (did this ever work?)
                    self.geodata_server.createGroups(self._layerGroups(),
                                                     self.layer_ids)
                except Exception as err:
                    # TODO: figure out where to properly put a warning or error message for this
                    feedback.logError(f"Could not create layer groups: {err}")
                finally:
                    try:
                        # Call closePublishing(): for GeoServer, this will set up vector tiles, if enabled
                        self.geodata_server.closePublishing()
                    except Exception as err:
                        feedback.logError(
                            f"Failed to finalize publish task: {err}")
                    self.stepFinished.emit(None, GROUPS)
            else:
                self.stepSkipped.emit(None, GROUPS)

            return True
        except Exception:
            self.exc_type, _, _ = sys.exc_info()
            self.exception = traceback.format_exc()
            return False