def __init__(self, configuration=None, baseurl=""): self.conf_info = configuration or {} self.resource_set = MemResourceSetDB() self.rpt = {} self.baseurl = baseurl if not self.baseurl.endswith("/"): self.baseurl += "/" self.session = Session() self.permit = Permission() self.permission_requests = PermissionRequests() self.map_rsid_id = {} self.map_id_rsid = {} self.map_user_id = {} self.eid2rpt = {} self.get_requester = get_requester
def __init__(self, keyjar, rpt_lifetime, iss, ressrv_id, rsr_path, ticket_lifetime=3600): # database with all permission requests self.permission_requests = PermissionRequests() # database with all authorization decisions self.authz_db = AuthzDB() # database with all registered permissions self.permit = Permission() # database with all the registered resource sets self.resource_set = MemResourceSetDB( rsr_path=rsr_path, delete_rsid=self.permit.delete_rsid) self.map_rsid_id = {} self.map_id_rsid = {} self.map_user_id = {} self.rpt_factory = JWT(keyjar, lifetime=rpt_lifetime, iss=iss) self.ticket_factory = JWT(keyjar, lifetime=ticket_lifetime, iss=iss) self.authzdesc_lifetime = 3600 self.client_id = ressrv_id self.rsr_path = rsr_path self.ad2rpt = {} self.rpt2adid = {}
class ADB(object): """ Expects to be one ADB instance per Resource Server """ def __init__(self, keyjar, rpt_lifetime, iss, ressrv_id, rsr_path, ticket_lifetime=3600): # database with all permission requests self.permission_requests = PermissionRequests() # database with all authorization decisions self.authz_db = AuthzDB() # database with all registered permissions self.permit = Permission() # database with all the registered resource sets self.resource_set = MemResourceSetDB( rsr_path=rsr_path, delete_rsid=self.permit.delete_rsid) self.map_rsid_id = {} self.map_id_rsid = {} self.map_user_id = {} self.rpt_factory = JWT(keyjar, lifetime=rpt_lifetime, iss=iss) self.ticket_factory = JWT(keyjar, lifetime=ticket_lifetime, iss=iss) self.authzdesc_lifetime = 3600 self.client_id = ressrv_id self.rsr_path = rsr_path self.ad2rpt = {} self.rpt2adid = {} def pending_permission_requests(self, owner, user): """ Return outstanding permission requests that is known to belong to an owner and bound to a requestor. :param owner: :param user: :return: """ res = [] for tick in self.permission_requests.requestor2tickets(user): rsid = self.permission_requests.ticket2rsid(tick) if self.resource_set.belongs_to(rsid, owner): res.append(tick) return res def is_expired(self, tinfo): if utc_time_sans_frac() <= tinfo['exp']: return False return True def permission_request_allowed(self, ticket, identity): """ Verify that whatever permission requests the ticket represents they are now allow. :param ticket: The ticket :param identity: Who has the ticket :return: Dictionary, with permission request as key and identifiers of authz decisions that permits the requests as values. """ _tinfo = self.rpt_factory.unpack(ticket) if self.is_expired(_tinfo): raise TicketError('expired', '{} > {}'.format(utc_time_sans_frac(), _tinfo['exp'])) try: prrs = self.permission_requests[ticket] except KeyError: logger.warning("Someone is using a ticket that doesn't exist") raise TicketError('invalid', ticket) else: result = {} for prr in prrs: owner = self.resource_set.owner(prr['resource_set_id']) _adids = self.authz_db.match(owner, identity, **prr.to_dict()) if not _adids: # all or nothing raise TicketError('not_authorized') result[prr.to_json()] = _adids return result def store_permission(self, permission, owner): """ Store a permission :param permission: The permission to store :param owner: The user setting the permission :return: A permission ID """ max_scopes = self.resource_set.read( owner, permission['resource_set_id'])["scopes"] # if no scopes are defined == all are requested try: _scopes = permission['scopes'] except KeyError: permission['scopes'] = max_scopes else: permission['scopes'] = [s for s in _scopes if s in max_scopes] pm = PermissionDescription(**permission) return self.authz_db.add(owner, perm_desc=pm) def register_permission(self, owner, rpt, rsid, scopes): """ :param owner: Resource owner :param rpt: Requesting party token :param rsid: Resource set id :param scopes: list of scopes """ now = utc_time_sans_frac() authz = AuthzDescription(resource_set_id=rsid, scopes=scopes, exp=now + self.authzdesc_lifetime, iat=now) self.permit.set(owner, rpt, authz) def resource_set_registration(self, method, owner, body=None, rsid=''): """ :param method: HTTP method :param owner: The owner of the resource set :param body: description of the resource set :param rsid: resource set id :return: tuple (http response code, http message, http response args) """ return self.resource_set.registration(method, owner, body, rsid) def issue_rpt(self, ticket, identity): """ As a side effect if a RPT is issued the ticket is removed and can not be used again. :param ticket: The ticket :param identity: Information about the entity who wants the RPT :return: A RPT """ idmap = self.permission_request_allowed(ticket, identity) if not idmap: return None rpt = self.rpt_factory.pack(aud=[self.client_id], type='rpt') for rsd in self.permission_requests[ticket]: owner = self.resource_set.owner(rsd['resource_set_id']) self.permit.bind_owner_to_rpt(owner, rpt) self.bind_rpt_to_authz_dec(rpt, idmap[rsd.to_json()]) self.register_permission(owner, rpt, rsd['resource_set_id'], rsd['scopes']) del self.permission_requests[ticket] return rpt def introspection(self, rpt): try: res = [] for owner in self.permit.rpt2owner[rpt]: res.extend(self.permit.get(owner, rpt)) return res except KeyError: return [] def bind_rpt_to_authz_dec(self, rpt, adid): for id in adid: try: self.ad2rpt[id].append(rpt) except KeyError: self.ad2rpt[id] = [rpt] try: self.rpt2adid[rpt].extend(adid) except KeyError: self.rpt2adid[rpt] = adid def remove_permission(self, owner, pid): """ :param owner: The owner of the resource set :param pid: The permission id """ # find all RPTs that has been issued based on this permission for rpt in self.ad2rpt[pid]: if self.rpt2adid[rpt] == [pid]: del self.rpt2adid[rpt] else: self.rpt2adid[rpt].remove(pid) self.permit.delete_rpt(rpt) del self.ad2rpt[pid] self.authz_db.delete(owner, pid=pid) def read_permission(self, owner, pid): return self.authz_db.read(owner, pid)
def create_db(self): self.perm_db = Permission() self.perm_db.init_owner('alice')
class TestPerm(object): @pytest.fixture(autouse=True) def create_db(self): self.perm_db = Permission() self.perm_db.init_owner('alice') def test_permisson_get(self): ad = AuthzDescription(resource_set_id='rsid0', scopes=['read', 'write']) self.perm_db.set('alice', 'rpt', ad) _spec = self.perm_db.get('alice', 'rpt') assert _spec[0]["scopes"] == ['read', 'write'] assert _spec[0]['resource_set_id'] == 'rsid0' def test_permisson_get_fail(self): ad = AuthzDescription(resource_set_id='rsid', scopes=['read', 'write']) self.perm_db.set('alice', 'rpt', ad) with pytest.raises(KeyError): self.perm_db.get('alice', 'rsid') with pytest.raises(KeyError): self.perm_db.get('alice', 'xxxx') def test_get_request_by_requestor(self): ad0 = AuthzDescription(resource_set_id='rsid0', scopes=['read', 'write']) ad1 = AuthzDescription(resource_set_id='rsid0', scopes=['read', 'exec']) self.perm_db.set('alice', 'rpt0', ad0) self.perm_db.set('alice', 'rpt1', ad1) _spec = self.perm_db.keys('alice') assert len(_spec) == 2 def test_get_requests(self): ad0 = AuthzDescription(resource_set_id='rsid0', scopes=['read', 'write']) ad1 = AuthzDescription(resource_set_id='rsid0', scopes=['read', 'exec']) self.perm_db.set('alice', 'rpt0', ad0) self.perm_db.set('alice', 'rpt1', ad1) _spec = self.perm_db.keys('alice') assert _eq(_spec, ['rpt0', 'rpt1']) def test_delete_request(self): ad0 = AuthzDescription(resource_set_id='rsid0', scopes=['read', 'write']) ad1 = AuthzDescription(resource_set_id='rsid0', scopes=['read', 'exec']) self.perm_db.set('alice', 'rpt_roger', ad0) self.perm_db.set('alice', 'rpt_bob1', ad0) self.perm_db.set('alice', 'rpt_bob2', ad1) _spec = self.perm_db.keys('alice') assert len(_spec) == 3 self.perm_db.delete_rpt('rpt_bob1') _spec = self.perm_db.keys('alice') assert _eq(_spec, ['rpt_roger', 'rpt_bob2']) _spec = self.perm_db.get('alice', 'rpt_bob2') assert len(_spec) == 1 def test_delete_request_by_resource_id(self): ad0 = AuthzDescription(resource_set_id='rsid0', scopes=['read', 'write']) ad1 = AuthzDescription(resource_set_id='rsid1', scopes=['read', 'exec']) self.perm_db.set('alice', 'rpt_roger', ad0) self.perm_db.set('alice', 'rpt_bob1', ad0) self.perm_db.set('alice', 'rpt_bob2', ad1) self.perm_db.delete_rsid('alice', 'rsid0') _spec = self.perm_db.keys('alice') assert _spec == ['rpt_bob2'] _spec = self.perm_db.get('alice', 'rpt_bob2') assert len(_spec) == 1
class UmaAS(object): endp = [ AuthorizationEndpoint, DynamicClientEndpoint, IntrospectionEndpoint, PermissionRegistrationEndpoint, RequestingPartyClaimsEndpoint, ResourceSetRegistrationEndpoint, RPTEndpoint, TokenEndpoint, ClientInfoEndpoint, ] def __init__(self, configuration=None, baseurl=""): self.conf_info = configuration or {} self.resource_set = MemResourceSetDB() self.rpt = {} self.baseurl = baseurl if not self.baseurl.endswith("/"): self.baseurl += "/" self.session = Session() self.permit = Permission() self.permission_requests = PermissionRequests() self.map_rsid_id = {} self.map_id_rsid = {} self.map_user_id = {} self.eid2rpt = {} self.get_requester = get_requester def endpoints(self): for endp in self.endp: yield endp(None).name def services(self): for endp in self.endp: yield endp.etype # def rpt_endpoint_(self, requestor, client_id, **kwargs): # """ # The endpoint URI at which the client asks the authorization server for # a RPT. # """ # #res = self.client_authentication(authn) # #if isinstance(res, Response): # # return res # # # create RPT, just overwrites whatever was there before # rpt = rndstr(32) # self.rpt[rpt] = {"requestor": requestor, "client_id": client_id} # self.session.set(rpt) # # msg = RPTResponse(rpt=rpt) # return Response(msg.to_json(), content="application/json") def resource_set_registration_endpoint_(self, entity, path, method, client_id, body="", if_match="", **kwargs): """ The endpoint at which the resource server handles resource sets descriptions. :param entity: The entity that controls the resource set :param path: :param method: HTTP method :param body: The resource set registration message :paran client_id: Which client I'm talking to :param if_match: The HTTP If-Match header if any :param kwargs: possible other arguments :returns: A Response instance """ # path should be /resource_set/{rsid} or /resource_set # Path may or may not start with '/' if path.startswith("/"): assert path[1:].startswith(RSR_PATH) rsid = path[PLEN + 1 :] else: assert path.startswith(RSR_PATH) rsid = path[PLEN:] if rsid.startswith("/"): rsid = rsid[1:] _user = safe_name(entity, client_id) logger.debug("handling resource set belonging to '%s'" % _user) # self.resource_set.set_collection(_user) if method == "POST": # create args = {"oid": _user, "data": body} func = self.resource_set.create elif method == "PUT": # update args = { "oid": _user, "data": body, "rsid": rsid, # "if_match": if_match } func = self.resource_set.update elif method == "GET": args = {"oid": _user} if not rsid: # List func = self.resource_set.list else: # Read func = self.resource_set.read args["rsid"] = rsid elif method == "DELETE": args = {"rsid": rsid, "oid": _user} func = self.resource_set.delete else: return BadRequest("Message error") logger.debug("operation: %s" % func) logger.debug("operation args: %s" % (args,)) try: body = func(**args) except MessageException as err: _err = ErrorResponse(error="invalid_request", error_description=str(err)) response = BadRequest(_err.to_json(), content="application/json") except UnknownObject: _err = ErrorResponse(error="not_found") response = NotFound(_err.to_json(), content="application/json") else: response = None if isinstance(body, ErrorResponse): pass else: if func == self.resource_set.delete: # As a side effect all permissions assigned that references # this resource set should be deleted self.permit.delete_permit_by_resource_id(entity, rsid) response = NoContent() elif func == self.resource_set.create: _etag = self.resource_set.etag[body["_id"]] response = Created( body.to_json(), content="application/json", headers=[("ETag", _etag), ("Location", "/{}/{}".format(RSR_PATH, body["_id"]))], ) elif func == self.resource_set.update: _etag = self.resource_set.etag[body["_id"]] response = NoContent(content="application/json", headers=[("ETag", _etag)]) elif func == self.resource_set.list: response = Response(json.dumps(body)) if not response: response = Response(body.to_json(), content="application/json") return response def _collapse(self, items): referenced = {} ibrsid = {} for item in items: try: for rsid in item["subsets"]: if rsid not in referenced: referenced[rsid] = 1 else: referenced[rsid] += 1 except KeyError: pass _rsid = self.map_id_rsid[item["_id"]] if _rsid not in referenced: referenced[_rsid] = 0 ibrsid[_rsid] = item res = [] for key, val in list(referenced.items()): if val == 0: res.append(ibrsid[key]) return res def resource_sets_by_user(self, entity, client_id, collapse=False): """ :param entity: The entity for which resource set descriptions has been registered. :return: A list of ResourceSetDescriptions """ res = [] _user = safe_name(entity, client_id) try: rss = self.resource_set.list(_user) except KeyError: return [] for _id in rss: try: res.append(self.resource_set.read(_user, _id)) except Exception: raise if collapse: res = self._collapse(res) return res # def resource_set_tree_by_rsid(self, owner, rsid): # rs = self.resource_set.read(owner, rsid) # _name = rs["name"].split("/")[-1] # try: # _rsids = rs["subsets"] # except KeyError: # return rsid, _name # else: # res = {} # for _rsid in _rsids: # _rs = self.resource_set_tree_by_rsid(owner, _rsid) # try: # res.update(_rs) # except ValueError: # try: # res.append(_rs) # except AttributeError: # res = [_rs] # # return {(rsid, _name): res} # # def resource_set_name(self, rsid): # rs = self.resource_set.read(self.map_rsid_id[rsid]) # return rs["name"] def permits_by_user(self, owner): """ :param owner: The owner of the resource :return: A dictionary with requestors as keys and permissions as values """ return self.permit.get_permits(owner) def authz_session_info(self, token): pass def introspection_endpoint_(self, entity, **kwargs): """ The endpoint URI at which the resource server introspects an RPT presented to it by a client. """ request = kwargs["request"] logger.debug("requestor: %s, request: %s" % (entity, request)) ir = IntrospectionRequest().from_json(request) owner = safe_name(entity, kwargs["client_id"]) try: try: # requestor = self.rpt[ir["token"]]["requestor"] perms = self.permit.get_accepted(owner, ir["token"]) except KeyError: response = BadRequest() else: if perms: irep = IntrospectionResponse(active=True, exp=perms[0]["exp"], permissions=perms) logger.debug("response: %s" % irep.to_json()) response = Response(irep.to_json(), content="application/json") else: logger.info("No permissions bound to this RPT") response = BadRequest() except ToOld: logger.info("RPT expired") irep = IntrospectionResponse(valid=False) response = Response(irep.to_json(), content="application/json") except KeyError: response = BadRequest() return response def permission_registration_endpoint_(self, entity, **kwargs): """ The endpoint URI at which the resource server registers a client-requested permission with the authorization server. This is a proposed permission waiting for the user to accept it. """ request = kwargs["request"] _ticket = rndstr(24) logging.debug("Registering permission request: %s" % request) resp = PermissionRegistrationResponse(ticket=_ticket) self.permission_requests.add_request(_ticket, request) return Created(resp.to_json(), content="application/json") def requesting_party_claims_endpoint(self, request="", **kwargs): """ The endpoint at which the resource server gathers the consent of the end-user resource owner or the client gathers the consent of the end-user requesting party, if the "authorization_code" grant type is used. """ pass def dynamic_client_endpoint(self, request="", **kwargs): pass def token_endpoint(self, request="", **kwargs): pass def authorization_endpoint(self, request="", **kwargs): pass @staticmethod def token_scope_check(areq, info): """ verifies that the scope that is demanded for the access token is one that I'm comfortable with. :param areq: AccessTokenRequest :param info: What's in the session db :return: None if OK otherwise and error response. """ try: assert areq["scope"] in info["scope"] except AssertionError: logger.error("Not the same scope as for the AuthzRequest") err = TokenErrorResponse(error="invalid_scope") return Response(err.to_json(), content="application/json") try: assert areq["scope"] in list(UMA_SCOPE.values()) except AssertionError: logger.error("Asked for scope which I don't deal with") err = TokenErrorResponse(error="invalid_scope") return Response(err.to_json(), content="application/json") return None def create_uma_providerinfo(self, pcr_class=ProviderConfiguration): kwargs = dict([(k, v) for k, v in list(self.conf_info.items()) if k in pcr_class.c_param]) _response = pcr_class(**kwargs) for endp in UmaAS.endp: _response.update(endpoint_ava(endp, self.baseurl)) logger.debug("provider_info_response: %s" % (_response.to_dict(),)) return _response # noinspection PyUnusedLocal def providerinfo_endpoint_(self, handle="", **kwargs): logger.debug("@providerinfo_endpoint") try: _response = self.create_uma_providerinfo() headers = [("Cache-Control", "no-store"), ("x-ffo", "bar")] # if handle: # (key, timestamp) = handle # if key.startswith(STR) and key.endswith(STR): # cookie = self.cookie_func(key, self.cookie_name, "pinfo", # self.sso_ttl) # headers.append(cookie) resp = Response(_response.to_json(), content="application/json", headers=headers) except Exception as err: message = traceback.format_exception(*sys.exc_info()) logger.error(message) resp = Response(message, content="html/text") return resp def get_subsets(self, owner, requestor, scopes): res = {} try: permits = self.permit.get_permit_by_requestor(owner, requestor) except KeyError: return res for permit, (_scopes, time_stamp) in list(permits.items()): _scs = [] for scope in scopes: try: assert scope in _scopes except AssertionError: pass else: _scs.append(scope) if _scs: res[permit] = _scs return res def register_permission(self, owner, rpt, rsid, scopes): now = utc_time_sans_frac() perm = AuthzDescription(resource_set_id=rsid, scopes=scopes, exp=now + self.session.lifetime, iat=now) self.permit.set_accepted(owner, rpt, perm) def rpt_endpoint_(self, entity, client_id, **kwargs): """ Registers an Authorization Description :param entity: Who's on the other side :param client_id: The UMA client :return: A Response instance """ adr = AuthorizationDataRequest().from_json(kwargs["request"]) # Get request permission that the resource server has registered try: prr_list = self.permission_requests.get_request(adr["ticket"]) except KeyError: errmsg = ErrorResponse(error="invalid_ticket") return BadRequest(errmsg.to_json(), content="application/json") self.permission_requests.del_request(adr["ticket"]) try: _rpt = adr["rpt"] except KeyError: _rpt = rndstr(32) for prr in prr_list: _rsid = prr["resource_set_id"] # Verify that the scopes are defined for the resource set owner = self.resource_set.rsid2oid[_rsid] rsd = self.resource_set.read(owner, _rsid) for scope in prr["scopes"]: try: assert scope in rsd["scopes"] except AssertionError: errmsg = ErrorResponse(error="not_authorized", error_description="Undefined scopes") return BadRequest(errmsg.to_json(), content="application/json") # Is there any permissions registered by the owner, if so verify # that it allows what is requested. Return what is allowed ! try: allow_scopes, timestamp = self.permit.get_permit(owner, entity, _rsid) except KeyError: # errmsg = ErrorResponse(error="not_authorized", error_description="No permission given") return BadRequest(errmsg.to_json(), content="application/json") else: _scopes = [] for scope in prr["scopes"]: try: assert scope in allow_scopes except AssertionError: pass else: _scopes.append(scope) # bind _requester to specific RPT for this user try: self.eid2rpt[owner][entity] = _rpt except KeyError: self.eid2rpt[owner] = {entity: _rpt} self.register_permission(owner, _rpt, _rsid, _scopes) rsp = AuthorizationDataResponse(rpt=_rpt) return Response(rsp.to_json()) def name2id(self, owner, requestor, rsid): _user = safe_name(owner, requestor) obj = self.resource_set.read(_user, rsid) return obj["_id"] def remove_permission(self, owner, requestor, resource_name): """ :param owner: The resource owner :param requestor: The SP entity ID :param resource_name: The name of the resource set """ _id = self.name2id(owner, requestor, resource_name) try: self.permit.delete_permit(owner, requestor, _id) except KeyError: pass try: _user = "******" % (owner, requestor) rm_rpt = self.permit.rm_accepted(_user, _id) except KeyError: pass else: for _rpt in rm_rpt: # immediate expiration self.session.update(_rpt, expires_at=0) def store_permission(self, user, requestor, rsids): """ :param user: The resource owner :param requestor: The requestor ID :param rsids: dictionary with Resource set IDs as keys and scopes as values """ logger.info("store: (%s, %s, %s)" % (user, requestor, rsids)) present = self.permit.get_rsid_permits(user, requestor) _new = [k for k in list(rsids.keys()) if k not in present] _user = safe_name(user, requestor) for rsid in _new: scopes = rsids[rsid] if scopes is None: rs = self.resource_set.read(_user, rsid) scopes = rs["scopes"] self.permit.set_permit(user, requestor, rsid, scopes) _rem = [k for k in present if k not in rsids] for rsid in _rem: self.permit.delete_permit(user, requestor, rsid) def read_permission(self, user, requestor, rsid): return self.permit.get_permit(user, requestor, rsid) def rec_rm_permission(self, user, requestor, rsid): """ If the resource set is a complex set, remove all subset permissions :param user: The owner of the resource :param requestor: Who the permission is applying to :param rsid: The resource set name """ _user = safe_name(user, requestor) rs = self.resource_set.read(_user, rsid) if "subsets" in rs: for ss in rs["subsets"]: self.rec_rm_permission(user, requestor, ss) try: self.permit.delete_permit(user, requestor, rsid) except KeyError: pass def rm_permission(self, user, requestor, rsid): """ If the resource set is a complex set, remove all subset permissions :param user: The owner of the resource :param requestor: Who the permission is applying to :param rsid: The resource set name """ logger.info("remove: (%s, %s, %s)" % (user, requestor, rsid)) self.rec_rm_permission(user, requestor, rsid) return True def rsid_permits(self, user, requestor): return self.permit.get_rsid_permits(user, requestor)
class UmaAS(object): endp = [ AuthorizationEndpoint, DynamicClientEndpoint, IntrospectionEndpoint, PermissionRegistrationEndpoint, RequestingPartyClaimsEndpoint, ResourceSetRegistrationEndpoint, RPTEndpoint, TokenEndpoint, ClientInfoEndpoint ] def __init__(self, configuration=None, baseurl=""): self.conf_info = configuration or {} self.resource_set = MemResourceSetDB() self.rpt = {} self.baseurl = baseurl if not self.baseurl.endswith("/"): self.baseurl += "/" self.session = Session() self.permit = Permission() self.permission_requests = PermissionRequests() self.map_rsid_id = {} self.map_id_rsid = {} self.map_user_id = {} self.eid2rpt = {} self.get_requester = get_requester def endpoints(self): for endp in self.endp: yield endp(None).name def services(self): for endp in self.endp: yield endp.etype # def rpt_endpoint_(self, requestor, client_id, **kwargs): # """ # The endpoint URI at which the client asks the authorization server for # a RPT. # """ # #res = self.client_authentication(authn) # #if isinstance(res, Response): # # return res # # # create RPT, just overwrites whatever was there before # rpt = rndstr(32) # self.rpt[rpt] = {"requestor": requestor, "client_id": client_id} # self.session.set(rpt) # # msg = RPTResponse(rpt=rpt) # return Response(msg.to_json(), content="application/json") def resource_set_registration_endpoint_(self, entity, path, method, client_id, body="", if_match="", **kwargs): """ The endpoint at which the resource server handles resource sets descriptions. :param entity: The entity that controls the resource set :param path: :param method: HTTP method :param body: The resource set registration message :paran client_id: Which client I'm talking to :param if_match: The HTTP If-Match header if any :param kwargs: possible other arguments :returns: A Response instance """ # path should be /resource_set/{rsid} or /resource_set # Path may or may not start with '/' if path.startswith("/"): assert path[1:].startswith(RSR_PATH) rsid = path[PLEN + 1:] else: assert path.startswith(RSR_PATH) rsid = path[PLEN:] if rsid.startswith("/"): rsid = rsid[1:] _user = safe_name(entity, client_id) logger.debug("handling resource set belonging to '%s'" % _user) # self.resource_set.set_collection(_user) if method == "POST": # create args = {"oid": _user, "data": body} func = self.resource_set.create elif method == "PUT": # update args = { "oid": _user, "data": body, "rsid": rsid, # "if_match": if_match } func = self.resource_set.update elif method == "GET": args = {"oid": _user} if not rsid: # List func = self.resource_set.list else: # Read func = self.resource_set.read args["rsid"] = rsid elif method == "DELETE": args = {"rsid": rsid, "oid": _user} func = self.resource_set.delete else: return BadRequest("Message error") logger.debug("operation: %s" % func) logger.debug("operation args: %s" % (args, )) try: body = func(**args) except MessageException as err: _err = ErrorResponse(error="invalid_request", error_description=str(err)) response = BadRequest(_err.to_json(), content="application/json") except UnknownObject: _err = ErrorResponse(error="not_found") response = NotFound(_err.to_json(), content="application/json") else: response = None if isinstance(body, ErrorResponse): pass else: if func == self.resource_set.delete: # As a side effect all permissions assigned that references # this resource set should be deleted self.permit.delete_permit_by_resource_id(entity, rsid) response = NoContent() elif func == self.resource_set.create: _etag = self.resource_set.etag[body["_id"]] response = Created(body.to_json(), content="application/json", headers=[("ETag", _etag), ("Location", "/{}/{}".format( RSR_PATH, body["_id"]))]) elif func == self.resource_set.update: _etag = self.resource_set.etag[body["_id"]] response = NoContent(content="application/json", headers=[("ETag", _etag)]) elif func == self.resource_set.list: response = Response(json.dumps(body)) if not response: response = Response(body.to_json(), content="application/json") return response def _collapse(self, items): referenced = {} ibrsid = {} for item in items: try: for rsid in item["subsets"]: if rsid not in referenced: referenced[rsid] = 1 else: referenced[rsid] += 1 except KeyError: pass _rsid = self.map_id_rsid[item["_id"]] if _rsid not in referenced: referenced[_rsid] = 0 ibrsid[_rsid] = item res = [] for key, val in list(referenced.items()): if val == 0: res.append(ibrsid[key]) return res def resource_sets_by_user(self, entity, client_id, collapse=False): """ :param entity: The entity for which resource set descriptions has been registered. :return: A list of ResourceSetDescriptions """ res = [] _user = safe_name(entity, client_id) try: rss = self.resource_set.list(_user) except KeyError: return [] for _id in rss: try: res.append(self.resource_set.read(_user, _id)) except Exception: raise if collapse: res = self._collapse(res) return res # def resource_set_tree_by_rsid(self, owner, rsid): # rs = self.resource_set.read(owner, rsid) # _name = rs["name"].split("/")[-1] # try: # _rsids = rs["subsets"] # except KeyError: # return rsid, _name # else: # res = {} # for _rsid in _rsids: # _rs = self.resource_set_tree_by_rsid(owner, _rsid) # try: # res.update(_rs) # except ValueError: # try: # res.append(_rs) # except AttributeError: # res = [_rs] # # return {(rsid, _name): res} # # def resource_set_name(self, rsid): # rs = self.resource_set.read(self.map_rsid_id[rsid]) # return rs["name"] def permits_by_user(self, owner): """ :param owner: The owner of the resource :return: A dictionary with requestors as keys and permissions as values """ return self.permit.get_permits(owner) def authz_session_info(self, token): pass def introspection_endpoint_(self, entity, **kwargs): """ The endpoint URI at which the resource server introspects an RPT presented to it by a client. """ request = kwargs["request"] logger.debug("requestor: %s, request: %s" % (entity, request)) ir = IntrospectionRequest().from_json(request) owner = safe_name(entity, kwargs["client_id"]) try: try: # requestor = self.rpt[ir["token"]]["requestor"] perms = self.permit.get_accepted(owner, ir["token"]) except KeyError: response = BadRequest() else: if perms: irep = IntrospectionResponse(active=True, exp=perms[0]["exp"], permissions=perms) logger.debug("response: %s" % irep.to_json()) response = Response(irep.to_json(), content="application/json") else: logger.info("No permissions bound to this RPT") response = BadRequest() except ToOld: logger.info("RPT expired") irep = IntrospectionResponse(valid=False) response = Response(irep.to_json(), content="application/json") except KeyError: response = BadRequest() return response def permission_registration_endpoint_(self, entity, **kwargs): """ The endpoint URI at which the resource server registers a client-requested permission with the authorization server. This is a proposed permission waiting for the user to accept it. """ request = kwargs["request"] _ticket = rndstr(24) logging.debug("Registering permission request: %s" % request) resp = PermissionRegistrationResponse(ticket=_ticket) self.permission_requests.add_request(_ticket, request) return Created(resp.to_json(), content="application/json") def requesting_party_claims_endpoint(self, request="", **kwargs): """ The endpoint at which the resource server gathers the consent of the end-user resource owner or the client gathers the consent of the end-user requesting party, if the "authorization_code" grant type is used. """ pass def dynamic_client_endpoint(self, request="", **kwargs): pass def token_endpoint(self, request="", **kwargs): pass def authorization_endpoint(self, request="", **kwargs): pass @staticmethod def token_scope_check(areq, info): """ verifies that the scope that is demanded for the access token is one that I'm comfortable with. :param areq: AccessTokenRequest :param info: What's in the session db :return: None if OK otherwise and error response. """ try: assert areq["scope"] in info["scope"] except AssertionError: logger.error("Not the same scope as for the AuthzRequest") err = TokenErrorResponse(error="invalid_scope") return Response(err.to_json(), content="application/json") try: assert areq["scope"] in list(UMA_SCOPE.values()) except AssertionError: logger.error("Asked for scope which I don't deal with") err = TokenErrorResponse(error="invalid_scope") return Response(err.to_json(), content="application/json") return None def create_uma_providerinfo(self, pcr_class=ProviderConfiguration): kwargs = dict([(k, v) for k, v in list(self.conf_info.items()) if k in pcr_class.c_param]) _response = pcr_class(**kwargs) for endp in UmaAS.endp: _response.update(endpoint_ava(endp, self.baseurl)) logger.debug("provider_info_response: %s" % (_response.to_dict(), )) return _response # noinspection PyUnusedLocal def providerinfo_endpoint_(self, handle="", **kwargs): logger.debug("@providerinfo_endpoint") try: _response = self.create_uma_providerinfo() headers = [("Cache-Control", "no-store"), ("x-ffo", "bar")] # if handle: # (key, timestamp) = handle # if key.startswith(STR) and key.endswith(STR): # cookie = self.cookie_func(key, self.cookie_name, "pinfo", # self.sso_ttl) # headers.append(cookie) resp = Response(_response.to_json(), content="application/json", headers=headers) except Exception as err: message = traceback.format_exception(*sys.exc_info()) logger.error(message) resp = Response(message, content="html/text") return resp def get_subsets(self, owner, requestor, scopes): res = {} try: permits = self.permit.get_permit_by_requestor(owner, requestor) except KeyError: return res for permit, (_scopes, time_stamp) in list(permits.items()): _scs = [] for scope in scopes: try: assert scope in _scopes except AssertionError: pass else: _scs.append(scope) if _scs: res[permit] = _scs return res def register_permission(self, owner, rpt, rsid, scopes): now = utc_time_sans_frac() perm = AuthzDescription(resource_set_id=rsid, scopes=scopes, exp=now + self.session.lifetime, iat=now) self.permit.set_accepted(owner, rpt, perm) def rpt_endpoint_(self, entity, client_id, **kwargs): """ Registers an Authorization Description :param entity: Who's on the other side :param client_id: The UMA client :return: A Response instance """ adr = AuthorizationDataRequest().from_json(kwargs["request"]) # Get request permission that the resource server has registered try: prr_list = self.permission_requests.get_request(adr["ticket"]) except KeyError: errmsg = ErrorResponse(error="invalid_ticket") return BadRequest(errmsg.to_json(), content="application/json") self.permission_requests.del_request(adr["ticket"]) try: _rpt = adr["rpt"] except KeyError: _rpt = rndstr(32) for prr in prr_list: _rsid = prr["resource_set_id"] # Verify that the scopes are defined for the resource set owner = self.resource_set.rsid2oid[_rsid] rsd = self.resource_set.read(owner, _rsid) for scope in prr["scopes"]: try: assert scope in rsd["scopes"] except AssertionError: errmsg = ErrorResponse( error="not_authorized", error_description="Undefined scopes") return BadRequest(errmsg.to_json(), content="application/json") # Is there any permissions registered by the owner, if so verify # that it allows what is requested. Return what is allowed ! try: allow_scopes, timestamp = self.permit.get_permit( owner, entity, _rsid) except KeyError: # errmsg = ErrorResponse(error="not_authorized", error_description="No permission given") return BadRequest(errmsg.to_json(), content="application/json") else: _scopes = [] for scope in prr["scopes"]: try: assert scope in allow_scopes except AssertionError: pass else: _scopes.append(scope) # bind _requester to specific RPT for this user try: self.eid2rpt[owner][entity] = _rpt except KeyError: self.eid2rpt[owner] = {entity: _rpt} self.register_permission(owner, _rpt, _rsid, _scopes) rsp = AuthorizationDataResponse(rpt=_rpt) return Response(rsp.to_json()) def name2id(self, owner, requestor, rsid): _user = safe_name(owner, requestor) obj = self.resource_set.read(_user, rsid) return obj["_id"] def remove_permission(self, owner, requestor, resource_name): """ :param owner: The resource owner :param requestor: The SP entity ID :param resource_name: The name of the resource set """ _id = self.name2id(owner, requestor, resource_name) try: self.permit.delete_permit(owner, requestor, _id) except KeyError: pass try: _user = "******" % (owner, requestor) rm_rpt = self.permit.rm_accepted(_user, _id) except KeyError: pass else: for _rpt in rm_rpt: # immediate expiration self.session.update(_rpt, expires_at=0) def store_permission(self, user, requestor, rsids): """ :param user: The resource owner :param requestor: The requestor ID :param rsids: dictionary with Resource set IDs as keys and scopes as values """ logger.info("store: (%s, %s, %s)" % (user, requestor, rsids)) present = self.permit.get_rsid_permits(user, requestor) _new = [k for k in list(rsids.keys()) if k not in present] _user = safe_name(user, requestor) for rsid in _new: scopes = rsids[rsid] if scopes is None: rs = self.resource_set.read(_user, rsid) scopes = rs["scopes"] self.permit.set_permit(user, requestor, rsid, scopes) _rem = [k for k in present if k not in rsids] for rsid in _rem: self.permit.delete_permit(user, requestor, rsid) def read_permission(self, user, requestor, rsid): return self.permit.get_permit(user, requestor, rsid) def rec_rm_permission(self, user, requestor, rsid): """ If the resource set is a complex set, remove all subset permissions :param user: The owner of the resource :param requestor: Who the permission is applying to :param rsid: The resource set name """ _user = safe_name(user, requestor) rs = self.resource_set.read(_user, rsid) if "subsets" in rs: for ss in rs["subsets"]: self.rec_rm_permission(user, requestor, ss) try: self.permit.delete_permit(user, requestor, rsid) except KeyError: pass def rm_permission(self, user, requestor, rsid): """ If the resource set is a complex set, remove all subset permissions :param user: The owner of the resource :param requestor: Who the permission is applying to :param rsid: The resource set name """ logger.info("remove: (%s, %s, %s)" % (user, requestor, rsid)) self.rec_rm_permission(user, requestor, rsid) return True def rsid_permits(self, user, requestor): return self.permit.get_rsid_permits(user, requestor)