def check_admin(user_lookup: KBaseUserLookup, token: Optional[str], perm: AdminPermission, method: str, log_fn: Callable[[str], None], as_user: UserID = None, skip_check: bool = False) -> bool: ''' Check whether a user has admin privileges. The request is logged. :param user_lookup: the service to use to look up user information. :param token: the user's token, or None if the user is anonymous. In this case, if skip_check is false, an UnauthorizedError will be thrown. :param perm: the required administration permission. :param method: the method the user is trying to run. This is used in logging and error messages. :param logger: a function that logs information when called with a string. :param as_user: if the admin is impersonating another user, the username of that user. :param skip_check: Skip the administration permission check and return false. :returns: true if the user has the required administration permission, false if skip_check is true. :raises UnauthorizedError: if the user does not have the permission required. :raises InvalidUserError: if any of the user names are invalid. :raises UnauthorizedError: if any of the users names are valid but do not exist in the system. ''' if skip_check: return False if not token: raise _UnauthorizedError( 'Anonymous users may not act as service administrators.') _not_falsy(method, 'method') _not_falsy(log_fn, 'log_fn') if _not_falsy(perm, 'perm') == AdminPermission.NONE: raise ValueError( 'what are you doing calling this method with no permission ' + 'requirement? That totally makes no sense. Get a brain moran') if as_user and perm != AdminPermission.FULL: raise ValueError('as_user is supplied, but permission is not FULL') p, user = _not_falsy(user_lookup, 'user_lookup').is_admin(token) if p < perm: err = (f'User {user} does not have the necessary administration ' + f'privileges to run method {method}') log_fn(err) raise _UnauthorizedError(err) if as_user and user_lookup.invalid_users([as_user ]): # returns list of bad users raise _NoSuchUserError(as_user.id) log_fn( f'User {user} is running method {method} with administration permission {p.name}' + (f' as user {as_user}' if as_user else '')) return True
def _check_perms(self, id_: UUID, user: UserID, access: _SampleAccessType, acls: SampleACL = None, as_admin: bool = False): if as_admin: return if not acls: acls = self._storage.get_sample_acls(id_) level = self._get_access_level(acls, user) if level < access: errmsg = f'User {user} {self._unauth_errmap[access]} sample {id_}' raise _UnauthorizedError(errmsg)
def _check_batch_perms( self, ids_: List[UUID], user: Optional[UserID], access: _SampleAccessType, acls: List[SampleACL] = None, as_admin: bool = False): if as_admin: return if not acls: acls = self._storage.get_sample_set_acls(ids_) levels = [self._get_access_level(acl, user) for acl in acls] for i, level in enumerate(levels): if level < access: uerr = f'User {user}' if user else 'Anonymous users' errmsg = f'{uerr} {self._unauth_errmap[access]} sample {ids_[i]}' raise _UnauthorizedError(errmsg)
def is_update(self, update: SampleACLDelta) -> bool: ''' Check if an acl delta update is actually an update or a noop for the sample. :param update: the update. :returns: True if the update would change the ACLs, False if not. The timestamp is not considered. :raises UnauthorizedError: if the update would affect the owner and update.at_least is not true, or if the owner is in the remove list regardless of at_least. ''' _not_falsy(update, 'update') o = self.owner ownerchange = o in update.admin or o in update.write or o in update.read if (ownerchange and not update.at_least) or o in update.remove: raise _UnauthorizedError( f'ACLs for the sample owner {o.id} may not be modified by a delta update.' ) rem = set(update.remove) admin = set(self.admin) write = set(self.write) read = set(self.read) # check if users are removed if not rem.isdisjoint(admin) or not rem.isdisjoint( write) or not rem.isdisjoint(read): return True # check if public read is changed if update.public_read is not None and update.public_read is not self.public_read: return True uadmin = set(update.admin) uwrite = set(update.write) uread = set(update.read) owner = set([o]) # check if users' permission is changed if update.at_least: return (not uadmin.issubset(admin | owner) or not uwrite.issubset(write | admin | owner) or not uread.issubset(read | write | admin | owner)) else: return (not uadmin.issubset(admin) or not uwrite.issubset(write) or not uread.issubset(read))
def has_permission(self, user: UserID, perm: WorkspaceAccessType, workspace_id: int = None, upa: UPA = None): ''' Check if a user can access a workspace resource. Exactly one of workspace_id or upa must be supplied - if both are supplied workspace_id takes precedence. Beware - passing a NONE permission will not throw errors unless the object or workspace does not exist. The user is not checked for existence. :param user: The user's user name. :param perm: The requested permission :param workspace_id: The ID of the workspace. :param upa: a workspace service UPA. :raises IllegalParameterError: if the wsid is illegal. :raises UnauthorizedError: if the user doesn't have the requested permission. :raises NoSuchWorkspaceDataError: if the workspace or UPA doesn't exist. ''' _not_falsy(user, 'user') _not_falsy(perm, 'perm') if workspace_id is not None: wsid = workspace_id name = 'workspace' target = str(workspace_id) upa = None elif upa: wsid = upa.wsid name = 'upa' target = str(upa) else: raise ValueError('Either an UPA or a workpace ID must be supplied') if wsid < 1: raise _IllegalParameterError(f'{wsid} is not a valid workspace ID') try: p = self._ws.administer({ 'command': 'getPermissionsMass', 'params': { 'workspaces': [{ 'id': wsid }] } }) except _ServerError as se: # this is pretty ugly, need error codes if 'No workspace' in se.args[0] or 'is deleted' in se.args[0]: raise _NoSuchWorkspaceDataError(se.args[0]) from se else: raise # could optimize a bit if NONE and upa but not worth the code complication most likely if (perm != WorkspaceAccessType.NONE and p['perms'][0].get(user.id) not in _PERM_TO_PERM_SET[perm]): raise _UnauthorizedError( f'User {user} cannot {_PERM_TO_PERM_TEXT[perm]} {name} {target}' ) if upa: # Allow any server errors to percolate upwards # theoretically the workspace could've been deleted between the last call and this # one, but that'll just result in a different error and is extremely unlikely to # happen, so don't worry about it ret = self._ws.administer({ 'command': 'getObjectInfo', 'params': { 'objects': [{ 'ref': str(upa) }], 'ignoreErrors': 1 } }) if not ret['infos'][0]: raise _NoSuchWorkspaceDataError(f'Object {upa} does not exist')