Esempio n. 1
0
 def _modify_namespace_users(self, add: bool, namespace_id, admin_user):
     """
     :param add: True to add the user to the namespace, False to remove.
     """
     not_none(namespace_id, 'namespace_id')
     not_none(admin_user, 'admin_user')
     op = '$addToSet' if add else '$pull'
     try:
         res = self._db[_COL_NAMESPACES].update_one(
             {_FLD_NS_ID: namespace_id.id}, {
                 op: {
                     _FLD_USERS: {
                         _FLD_AUTHSOURCE: admin_user.authsource_id.id,
                         _FLD_NAME: admin_user.username.name
                     }
                 }
             })
         if res.matched_count != 1:
             raise NoSuchNamespaceError(namespace_id.id)
         if res.modified_count != 1:
             action = 'already administrates' if add else 'does not administrate'
             ex = UserExistsError if add else NoSuchUserError  # might want diff exceps here
             raise ex('User {}/{} {} namespace {}'.format(
                 admin_user.authsource_id.id, admin_user.username.name,
                 action, namespace_id.id))
     except PyMongoError as e:
         raise IDMappingStorageError('Connection to database failed: ' +
                                     str(e)) from e
Esempio n. 2
0
    def set_namespace_publicly_mappable(self, authsource_id: AuthsourceID,
                                        token: Token,
                                        namespace_id: NamespaceID,
                                        publicly_mappable: bool) -> None:
        """
        Set a namespace to be publicly mappable, or remove that state. A publicly mappable
        namespace may have ID mappings added to it without the user being an administrator
        of the namespace. The user must always be an administrator of the primary ID of the ID
        tuple.

        :param authsource_id: The authentication source to be used to look up the user token.
        :param token: the user's token.
        :param namespace_id: the ID of the namespace to modify.
        :param publicly_mappable: True to set the namespace to publicly mappable, false otherwise.
        :raises TypeError: if authsource ID, namespace ID, or token are None.
        :raises NoSuchAuthsourceError: if there's no lookup handler for the provided authsource.
        :raises InvalidTokenError: if the token is invalid.
        :raises NoSuchNamespaceError: if the namespace does not exist.
        :raises UnauthorizedError: if the user is not authorized to administrate the namespace.
        """
        not_none(token, 'token')
        not_none(namespace_id, 'namespace_id')
        user, _ = self._lookup.get_user(authsource_id, token)
        self._check_authed_for_ns_get(user, namespace_id)
        self._storage.set_namespace_publicly_mappable(namespace_id,
                                                      publicly_mappable)
        _log('User %s/%s set namespace %s public map property to %s',
             user.authsource_id.id, user.username.name, namespace_id.id,
             publicly_mappable)
Esempio n. 3
0
 def _check_authsource_id(self, authsource_id: AuthsourceID) -> None:
     """
     :raises NoSuchAuthsourceError: if there's no handler for the provided authsource.
     """
     not_none(authsource_id, 'authsource_id')
     if authsource_id not in self._lookup:
         raise NoSuchAuthsourceError(authsource_id.id)
Esempio n. 4
0
    def get_namespace(self,
                      namespace_id: NamespaceID,
                      authsource_id: AuthsourceID = None,
                      token: Token = None) -> Namespace:
        """
        Get a namespace. If user credentials are provided and the user is a system admin or an
        admin of the namespace, the namespace user list will be returned. Otherwise, the user
        list will be empty.

        :param namespace_id: the ID of the namespace to get.
        :param authsource_id: the authsource of the provided token.
        :param token: the user's token.
        :raises TypeError: if the namespace ID is None or only one of the authsource ID or token
            are supplied.
        :raises NoSuchNamespaceError: if the namespace does not exist.
        :raises NoSuchAuthsourceError: if there's no lookup handler for the provided authsource.
        :raises InvalidTokenError: if the token is invalid.
        """
        not_none(namespace_id, 'namespace_id')
        if bool(authsource_id) ^ bool(token):  # xor
            raise TypeError(
                'If token or authsource_id is specified, both must be specified'
            )
        ns = self._storage.get_namespace(namespace_id)
        if token:
            authsource_id = cast(
                AuthsourceID, authsource_id)  # mypy doesn't understand the xor
            user, admin = self._lookup.get_user(authsource_id, token)
            if admin or user in ns.authed_users:
                return ns
        return ns.without_users()
Esempio n. 5
0
    def get_mappings(
        self,
        oid: ObjectID,
        ns_filter: Iterable[NamespaceID] = None
    ) -> Tuple[Set[ObjectID], Set[ObjectID]]:
        """
        Find mappings given a namespace / id combination.

        If the id does not exist, no results will be returned.

        :param oid: the namespace / id combination to match against.
        :param ns_filter: a list of namespaces with which to filter the results. Only results in
            these namespaces will be returned.
        :returns: a tuple of sets of object IDs. The first set in the tuple contains mappings
            where the provided object ID is the administrative object ID, and the second set
            contains the remainder of the mappings.
        :raise TypeError: if the object ID is None or the filter contains None.
        :raise NoSuchNamespaceError: if any of the namespaces do not exist.
        """
        not_none(oid, 'oid')
        check = [oid.namespace_id]
        if ns_filter:
            no_Nones_in_iterable(ns_filter, 'ns_filter')
            check.extend(ns_filter)
        self._storage.get_namespaces(check)  # check for existence
        return self._storage.find_mappings(oid, ns_filter=ns_filter)
Esempio n. 6
0
    def add_user_to_namespace(self, authsource_id: AuthsourceID, token: Token,
                              namespace_id: NamespaceID, user: User) -> None:
        """
        Add a user to a namespace.

        :param authsource_id: The authentication source to be used to look up the user token.
        :param token: the user's token.
        :param namespace_id: the namespace to modify.
        :param user: the user.
        :raises TypeError: if any of the arguments are None.
        :raises NoSuchAuthsourceError: if there's no handler for the provided authsource ID or the
            user's authsource.
        :raises NoSuchNamespaceError: if the namespace does not exist.
        :raises NoSuchUserError: if the user is invalid according to the appropriate user handler.
        :raises UserExistsError: if the user already administrates the namespace.
        :raises InvalidTokenError: if the token is invalid.
        :raises UnauthorizedError: if the user is not a system administrator.
        """
        not_none(namespace_id, 'namespace_id')
        not_none(user, 'user')
        admin = self._check_sys_admin(authsource_id, token)
        self._check_valid_user(user)
        self._storage.add_user_to_namespace(namespace_id, user)
        _log('Admin %s/%s added user %s/%s to namespace %s',
             admin.authsource_id.id, admin.username.name,
             user.authsource_id.id, user.username.name, namespace_id.id)
Esempio n. 7
0
    def __init__(self, storage: IDMappingStorage) -> None:
        '''
        Create a local user handler.

        :param storage: the storage system in which users are stored.
        '''
        not_none(storage, 'storage')
        self._store = storage
Esempio n. 8
0
 def user_exists(self, username: Username) -> bool:
     not_none(username, 'username')
     try:
         return self._db[_COL_USERS].count_documents(
             {_FLD_USER: username.name}) == 1
     except PyMongoError as e:
         raise IDMappingStorageError('Connection to database failed: ' +
                                     str(e)) from e
Esempio n. 9
0
 def is_valid_user(
         self,
         username: Username) -> Tuple[bool, Optional[int], Optional[int]]:
     not_none(username, 'username')
     r = requests.get(self.auth_url + 'api/V2/users/?list=' + username.name,
                      headers={'Authorization': self._token.token})
     self._check_error(r)
     j = r.json()
     return (len(j) == 1, None, 3600)
Esempio n. 10
0
 def _check_valid_user(self, user):
     """
     :raises NoSuchAuthsourceError: if there's no handler for the user's authsource.
     :raises NoSuchUserError: if the user is invalid according to the appropriate user handler.
     """
     not_none(user, 'user')
     if not self._lookup.is_valid_user(user):
         raise NoSuchUserError('{}/{}'.format(user.authsource_id.id,
                                              user.username.name))
Esempio n. 11
0
    def set_user_as_admin(self, username: Username, admin: bool) -> None:
        """
        Set or remove a local user's administration status.

        :param username: the name of the user to alter.
        :param admin: True to give the user admin privileges, False to remove them. If the user
            is already in the given state, no further action is taken.
        :raises TypeError: if the username is None.
        """
        not_none(username, 'username')
        self._store.set_local_user_as_admin(username, admin)
Esempio n. 12
0
 def remove_mapping(self, primary_OID: ObjectID,
                    secondary_OID: ObjectID) -> bool:
     not_none(primary_OID, 'primary_OID')
     not_none(secondary_OID, 'secondary_OID')
     try:
         res = self._db[_COL_MAPPINGS].delete_one(
             self.to_mapping_mongo_doc(primary_OID, secondary_OID))
         return res.deleted_count == 1
     except PyMongoError as e:
         raise IDMappingStorageError('Connection to database failed: ' +
                                     str(e)) from e
Esempio n. 13
0
    def __init__(self, db: Database) -> None:
        """
        Create a ID mapping storage system.

        :param db: the MongoDB database in which to store the mappings and other data.
        :raises StorageInitException: if the storage system could not be initialized properly.
        :raises TypeError: if the Mongo database is None.
        """
        not_none(db, 'db')
        self._db = db
        self._ensure_indexes()
        self._check_schema()  # MUST happen after ensuring indexes
Esempio n. 14
0
    def get_namespace(self, namespace_id: NamespaceID) -> Namespace:
        not_none(namespace_id, 'namespace_id')
        try:
            nsdoc = self._db[_COL_NAMESPACES].find_one(
                {_FLD_NS_ID: namespace_id.id})
        except PyMongoError as e:
            raise IDMappingStorageError('Connection to database failed: ' +
                                        str(e)) from e

        if not nsdoc:
            raise NoSuchNamespaceError(namespace_id.id)
        return self._to_ns(nsdoc)
Esempio n. 15
0
 def add_mapping(self, primary_OID: ObjectID,
                 secondary_OID: ObjectID) -> None:
     not_none(primary_OID, 'primary_OID')
     not_none(secondary_OID, 'secondary_OID')
     try:
         self._db[_COL_MAPPINGS].insert_one(
             self.to_mapping_mongo_doc(primary_OID, secondary_OID))
     except DuplicateKeyError as e:
         pass  # don't care, record is already there
     except PyMongoError as e:
         raise IDMappingStorageError('Connection to database failed: ' +
                                     str(e)) from e
Esempio n. 16
0
    def get_user(self, token: HashedToken) -> Tuple[Username, bool]:
        not_none(token, 'token')
        try:
            userdoc = self._db[_COL_USERS].find_one(
                {_FLD_TOKEN: token.token_hash}, {_FLD_TOKEN: 0})
        except PyMongoError as e:
            raise IDMappingStorageError('Connection to database failed: ' +
                                        str(e)) from e

        if not userdoc:
            raise InvalidTokenError()
        return (Username(userdoc[_FLD_USER]), userdoc[_FLD_ADMIN])
Esempio n. 17
0
    def create_user(self, username: Username) -> Token:
        '''
        Create a new user in the local storage system. Returns a new token for that user.

        :param username: The name of the user to create.
        :raises TypeError: if the user name is None.
        :raises UserExistsError: if the user already exists.
        '''
        not_none(username, 'username')
        t = tokens.generate_token()
        self._store.create_local_user(username, t.get_hashed_token())
        return t
Esempio n. 18
0
    def new_token(self, username: Username) -> Token:
        '''
        Generate a new token for a user in the local storage system.

        :param username: The name of the user to update.
        :raises TypeError: if the user name is None.
        :raises NoSuchUserError: if the user does not exist.
        '''
        not_none(username, 'username')
        t = tokens.generate_token()
        self._store.update_local_user_token(username, t.get_hashed_token())
        return t
 def is_valid_user(
         self,
         username: Username) -> Tuple[bool, Optional[int], Optional[int]]:
     not_none(username, 'username')
     r = requests.get(self.auth_url + 'api/users/' + username.name +
                      '.json')
     if 200 <= r.status_code <= 299:
         return (True, None, 3600)
     if r.status_code == 410:  # Uses Gone to denote no such user
         return (False, None, 3600)
     raise IOError('Unexpected error from JGI server: ' +
                   str(r.status_code))
Esempio n. 20
0
    def __init__(self, authsource_id: AuthsourceID, username: Username) -> None:
        """
        Create a new user.

        :param authsource_id: The authentication source for the user.
        :param username: The name of the user.
        :raises TypeError: if any of the arguments are None.
        """
        not_none(authsource_id, 'authsource_id')
        not_none(username, 'username')
        self.authsource_id = authsource_id
        self.username = username
Esempio n. 21
0
 def create_namespace(self, namespace_id: NamespaceID) -> None:
     not_none(namespace_id, 'namespace_id')
     try:
         self._db[_COL_NAMESPACES].insert_one({
             _FLD_NS_ID: namespace_id.id,
             _FLD_PUB_MAP: False,
             _FLD_USERS: []
         })
     except DuplicateKeyError as e:
         raise NamespaceExistsError(namespace_id.id)
     except PyMongoError as e:
         raise IDMappingStorageError('Connection to database failed: ' +
                                     str(e)) from e
Esempio n. 22
0
 def set_local_user_as_admin(self, username: Username, admin: bool) -> None:
     not_none(username, 'username')
     admin = True if admin else False  # more readable than admin and True
     try:
         res = self._db[_COL_USERS].update_one(
             {_FLD_USER: username.name}, {'$set': {
                 _FLD_ADMIN: admin
             }})
         if res.matched_count != 1:  # don't care if user was updated or not, just found
             raise NoSuchUserError(username.name)
     except PyMongoError as e:
         raise IDMappingStorageError('Connection to database failed: ' +
                                     str(e)) from e
Esempio n. 23
0
 def set_namespace_publicly_mappable(self, namespace_id: NamespaceID,
                                     publicly_mappable: bool) -> None:
     not_none(namespace_id, 'namespace_id')
     pm = True if publicly_mappable else False  # more readable than 'and True'
     try:
         res = self._db[_COL_NAMESPACES].update_one(
             {_FLD_NS_ID: namespace_id.id}, {'$set': {
                 _FLD_PUB_MAP: pm
             }})
         if res.matched_count != 1:  # don't care if modified or not
             raise NoSuchNamespaceError(namespace_id.id)
     except PyMongoError as e:
         raise IDMappingStorageError('Connection to database failed: ' +
                                     str(e)) from e
Esempio n. 24
0
    def __init__(self, namespace_id: NamespaceID, data_id: str) -> None:
        """
        Create a new object ID.

        :param namespace_id: The ID of the namespace in which the data ID resides.
        :param data_id: The ID of the data unit, no more than 1000 characters.
        :raises TypeError: if the namespace ID is None.
        :raises MissingParameterError: if the data ID is None or whitespace only.
        :raises IllegalParameterError: if the data ID does not meet the requirements.
        """
        not_none(namespace_id, 'namespace_id')
        check_string(data_id, 'data id',
                     max_len=1000)  # should maybe check for control chars
        self.namespace_id = namespace_id
        self.id = data_id
 def get_user(
         self,
         token: Token) -> Tuple[User, bool, Optional[int], Optional[int]]:
     not_none(token, 'token')
     r = requests.get(self.auth_url + 'api/sessions/' + token.token +
                      '.json')
     if 400 <= r.status_code <= 499:
         raise InvalidTokenError(
             'JGI auth server reported token is invalid: ' +
             str(r.status_code))
     if 500 <= r.status_code <= 599:
         raise IOError('JGI auth server reported an internal error: ' +
                       str(r.status_code))
     sres = r.json()
     return (User(self._JGI,
                  Username(str(sres['user']['id']))), False, None, 300)
Esempio n. 26
0
 def get_user(
         self,
         token: Token) -> Tuple[User, bool, Optional[int], Optional[int]]:
     not_none(token, 'token')
     r = requests.get(self.auth_url + 'api/V2/token',
                      headers={'Authorization': token.token})
     self._check_error(r)
     tokenres = r.json()
     r = requests.get(self.auth_url + 'api/V2/me',
                      headers={'Authorization': token.token})
     self._check_error(r)
     mres = r.json()
     return (User(self._KBASE,
                  Username(tokenres['user'])), self._kbase_system_admin
             in mres['customroles'], tokenres['expires'] // 1000,
             tokenres['cachefor'] // 1000)
Esempio n. 27
0
    def __init__(self,
                 namespace_id: NamespaceID,
                 is_publicly_mappable: bool,
                 authed_users: Set[User] = None) -> None:
        '''
        Create a namespace.

        :param namespace_id: the ID of the namespace.
        :param is_publicly_mappable: whether the namespace is publicly mappable or not.
        :param authed_users: users that are authorized to administer the namespace.
        :raises TypeError: if namespace_id is None or authed_users contains None
        '''
        not_none(namespace_id, 'namespace_id')
        self.namespace_id = namespace_id
        self.is_publicly_mappable = is_publicly_mappable
        self.authed_users = frozenset(
            authed_users) if authed_users else frozenset()
        no_Nones_in_iterable(self.authed_users, 'authed_users')
Esempio n. 28
0
 def _check_sys_admin(self, authsource_id: AuthsourceID,
                      token: Token) -> User:
     """
     :raises NoSuchAuthsourceError: if there's no handler for the provided authsource.
     :raises InvalidTokenError: if the token is invalid.
     :raises UnauthorizedError: if the user is not a system administrator.
     """
     not_none(token, 'token')
     if authsource_id not in self._admin_authsources:
         raise UnauthorizedError(
             ('Auth source {} is not configured as a provider of ' +
              'system administration status').format(authsource_id.id))
     user, admin = self._lookup.get_user(authsource_id, token)
     if not admin:
         raise UnauthorizedError(
             'User {}/{} is not a system administrator'.format(
                 user.authsource_id.id, user.username.name))
     return user
Esempio n. 29
0
    def __init__(self, user_lookup: UserLookupSet,
                 admin_authsources: Set[AuthsourceID],
                 storage: IDMappingStorage) -> None:
        """
        Create the mapper.

        :param user_lookup: the set of user lookup handlers to query when looking up user names
            from tokens or checking that a provided user name is valid.
        :param admin_authsources: the set of auth sources that are valid system admin sources.
            The admin state returned by other auth sources will be ignored.
        :param storage: the mapping storage system.
        """
        not_none(user_lookup, 'user_lookup')
        no_Nones_in_iterable(admin_authsources, 'admin_authsources')
        not_none(storage, 'storage')
        self._storage = storage
        self._lookup = user_lookup
        self._admin_authsources = admin_authsources
Esempio n. 30
0
    def create_namespace(self, authsource_id: AuthsourceID, token: Token,
                         namespace_id: NamespaceID) -> None:
        """
        Create a namespace.

        :param authsource_id: The authentication source to be used to look up the user token.
        :param token: the user's token.
        :param namespace_id: The namespace to create.
        :raises TypeError: if any of the arguments are None.
        :raises NoSuchAuthsourceError: if there's no handler for the provided authsource.
        :raises NamespaceExistsError: if the namespace already exists.
        :raises InvalidTokenError: if the token is invalid.
        :raises UnauthorizedError: if the user is not a system administrator.
        """
        not_none(namespace_id, 'namespace_id')
        admin = self._check_sys_admin(authsource_id, token)
        self._storage.create_namespace(namespace_id)
        _log('Admin %s/%s created namespace %s', admin.authsource_id.id,
             admin.username.name, namespace_id.id)