def reset_password_flow(username, password, request): cache_key = get_account_cache_key(username, request.registry) hashed_password = utils.hmac_digest(cache_key, password) pwd_str = password.encode(encoding="utf-8") cached_password = get_cached_reset_password(username, request.registry) if not cached_password: return None # The temporary reset password is only available for changing a user's password. if request.method.lower() not in ["post", "put", "patch"]: return None # Only allow modifying a user account, no other resource. uri = utils.strip_uri_prefix(request.path) resource_name, _ = utils.view_lookup(request, uri) if resource_name != "account": return None try: data = request.json["data"] except (ValueError, KeyError): return None # Request one and only one data field: the `password`. if not data or "password" not in data or len(data.keys()) > 1: return None cached_password_str = cached_password.encode(encoding="utf-8") if bcrypt.checkpw(pwd_str, cached_password_str): # Remove the temporary reset password from the cache. delete_cached_reset_password(username, request.registry) cache_account(hashed_password, username, request.registry) return True
def permissions_get(request): # Invert the permissions inheritance tree. perms_descending_tree = {} for obtained, obtained_from in PERMISSIONS_INHERITANCE_TREE.items(): on_resource, obtained_perm = obtained.split(':', 1) for from_resource, perms in obtained_from.items(): for perm in perms: perms_descending_tree.setdefault(from_resource, {})\ .setdefault(perm, {})\ .setdefault(on_resource, set())\ .add(obtained_perm) # Obtain current principals. principals = request.effective_principals if Authenticated in principals: # Since this view does not require any permission (can be used to # obtain public users permissions), we have to add the prefixed userid # among the principals (see :mode:`kinto.core.authentication`) userid = request.prefixed_userid principals.append(userid) # Query every possible permission of the current user from backend. # Since there is no "full-list" method, we query for each possible # permission (read, write, group:create, collection:create, record:create). # XXX: could be optimized into one call to backend when needed. possible_perms = set([k.split(':', 1)[1] for k in PERMISSIONS_INHERITANCE_TREE.keys()]) backend = request.registry.permission perms_by_object_uri = {} for perm in possible_perms: object_uris = backend.get_accessible_objects(principals, perm) for object_uri in object_uris: perms_by_object_uri.setdefault(object_uri, []).append(perm) entries = [] for object_uri, perms in perms_by_object_uri.items(): # Obtain associated resource from object URI resource_name, matchdict = core_utils.view_lookup(request, object_uri) # For consistency with events payloads, prefix id with resource name matchdict[resource_name + '_id'] = matchdict.get('id') # Expand implicit permissions using descending tree. permissions = set(perms) for perm in perms: obtained = perms_descending_tree[resource_name][perm] # Related to same resource only and not every sub-objects. # (e.g "bucket:write" gives "bucket:read" but not "group:read") permissions |= obtained[resource_name] entry = dict(uri=object_uri, resource_name=resource_name, permissions=list(permissions), **matchdict) entries.append(entry) return {"data": entries}
def resource_create_object(request, resource_cls, uri): """Implicitly create a resource (or fail silently). In the default bucket, the bucket and collection are implicitly created. This helper creates one of those resources using a simulated request and context that is appropriate for the resource. Also runs create events as though the resource were created in a subrequest. If the resource already exists, do nothing. """ resource_name, matchdict = view_lookup(request, uri) # Build a fake request, mainly used to populate the create events that # will be triggered by the resource. fakerequest = build_request(request, { 'method': 'PUT', 'path': uri, }) fakerequest.matchdict = matchdict fakerequest.bound_data = request.bound_data fakerequest.authn_type = request.authn_type fakerequest.selected_userid = request.selected_userid fakerequest.errors = request.errors fakerequest.current_resource_name = resource_name obj_id = matchdict['id'] # Fake context, required to instantiate a resource. context = RouteFactory(fakerequest) context.resource_name = resource_name resource = resource_cls(fakerequest, context) # Check that provided id is valid for this resource. if not resource.model.id_generator.match(obj_id): error_details = { 'location': 'path', 'description': 'Invalid {} id'.format(resource_name) } raise_invalid(resource.request, **error_details) data = {'id': obj_id} try: obj = resource.model.create_record(data) except UnicityError as e: # The record already exists; skip running events return e.record # Since the current request is not a resource (but a straight Service), # we simulate a request on a resource. # This will be used in the resource event payload. resource.postprocess(obj, action=ACTIONS.CREATE) return obj
def permissions_get(request): # Invert the permissions inheritance tree. perms_descending_tree = {} for obtained, obtained_from in PERMISSIONS_INHERITANCE_TREE.items(): on_resource, obtained_perm = obtained.split(':', 1) for from_resource, perms in obtained_from.items(): for perm in perms: perms_descending_tree.setdefault(from_resource, {})\ .setdefault(perm, {})\ .setdefault(on_resource, set())\ .add(obtained_perm) # Obtain current principals. principals = request.effective_principals if Authenticated in principals: # Since this view does not require any permission (can be used to # obtain public users permissions), we have to add the prefixed userid # among the principals (see :mode:`kinto.core.authentication`) userid = request.prefixed_userid principals.append(userid) # Query every possible permission of the current user from backend. backend = request.registry.permission perms_by_object_uri = backend.get_accessible_objects(principals) entries = [] for object_uri, perms in perms_by_object_uri.items(): try: # Obtain associated resource from object URI resource_name, matchdict = core_utils.view_lookup( request, object_uri) except ValueError: # Skip permissions entries that are not linked to an object URI. continue # For consistency with events payloads, prefix id with resource name matchdict[resource_name + '_id'] = matchdict.get('id') # Expand implicit permissions using descending tree. permissions = set(perms) for perm in perms: obtained = perms_descending_tree[resource_name][perm] # Related to same resource only and not every sub-objects. # (e.g "bucket:write" gives "bucket:read" but not "group:read") permissions |= obtained[resource_name] entry = dict(uri=object_uri, resource_name=resource_name, permissions=list(permissions), **matchdict) entries.append(entry) return {"data": entries}
def resource_create_object(request, resource_cls, uri): """In the default bucket, the bucket and collection are implicitly created. This helper instantiate the resource and simulate a request with its RootFactory on the instantiated resource. :returns: the created object :rtype: dict """ resource_name, matchdict = view_lookup(request, uri) # Build a fake request, mainly used to populate the create events that # will be triggered by the resource. fakerequest = build_request(request, { 'method': 'PUT', 'path': uri, }) fakerequest.matchdict = matchdict fakerequest.bound_data = request.bound_data fakerequest.authn_type = request.authn_type fakerequest.selected_userid = request.selected_userid fakerequest.errors = request.errors fakerequest.current_resource_name = resource_name obj_id = matchdict['id'] # Fake context, required to instantiate a resource. context = RouteFactory(fakerequest) context.resource_name = resource_name resource = resource_cls(fakerequest, context) # Check that provided id is valid for this resource. if not resource.model.id_generator.match(obj_id): error_details = { 'location': 'path', 'description': "Invalid %s id" % resource_name } raise_invalid(resource.request, **error_details) data = {'id': obj_id} try: obj = resource.model.create_record(data) # Since the current request is not a resource (but a straight Service), # we simulate a request on a resource. # This will be used in the resource event payload. resource.postprocess(data, action=ACTIONS.CREATE) except storage_exceptions.UnicityError as e: obj = e.record return obj
def get_records(self, filters=None, sorting=None, pagination_rules=None, limit=None, include_deleted=False, parent_id=None): # Invert the permissions inheritance tree. perms_descending_tree = {} for on_resource, tree in PERMISSIONS_INHERITANCE_TREE.items(): for obtained_perm, obtained_from in tree.items(): for from_resource, perms in obtained_from.items(): for perm in perms: perms_descending_tree.setdefault(from_resource, {})\ .setdefault(perm, {})\ .setdefault(on_resource, set())\ .add(obtained_perm) # Obtain current principals. principals = self.request.prefixed_principals # Query every possible permission of the current user from backend. backend = self.request.registry.permission perms_by_object_uri = backend.get_accessible_objects(principals) # Check settings for every allowed resources. from_settings = allowed_from_settings(self.request.registry.settings, principals) # Expand permissions obtained from backend with the object URIs that # correspond to permissions allowed from settings. allowed_resources = {'bucket', 'collection', 'group'} & set( from_settings.keys()) if allowed_resources: storage = self.request.registry.storage every_bucket, _ = storage.get_all(parent_id='', collection_id='bucket') for bucket in every_bucket: bucket_uri = '/buckets/{id}'.format(**bucket) for res in allowed_resources: resource_perms = from_settings[res] # Bucket is always fetched. if res == 'bucket': perms_by_object_uri.setdefault( bucket_uri, set()).update(resource_perms) continue # Fetch bucket collections and groups. # XXX: wrong approach: query in a loop! every_subobjects, _ = storage.get_all(parent_id=bucket_uri, collection_id=res) for subobject in every_subobjects: subobj_uri = bucket_uri + '/{0}s/{1}'.format( res, subobject['id']) perms_by_object_uri.setdefault( subobj_uri, set()).update(resource_perms) entries = [] for object_uri, perms in perms_by_object_uri.items(): try: # Obtain associated res from object URI resource_name, matchdict = core_utils.view_lookup( self.request, object_uri) except ValueError: # Skip permissions entries that are not linked to an object URI continue # For consistency with event payloads, prefix id with resource name matchdict[resource_name + '_id'] = matchdict.get('id') # Expand implicit permissions using descending tree. permissions = set(perms) for perm in perms: obtained = perms_descending_tree[resource_name][perm] # Related to same resource only and not every sub-objects. # (e.g "bucket:write" gives "bucket:read" but not "group:read") permissions |= obtained[resource_name] entry = dict(uri=object_uri, resource_name=resource_name, permissions=list(permissions), **matchdict) entries.append(entry) return extract_record_set(entries, filters=filters, sorting=sorting, pagination_rules=pagination_rules, limit=limit)
def get_records( self, filters=None, sorting=None, pagination_rules=None, limit=None, include_deleted=False, parent_id=None, ): # Invert the permissions inheritance tree. perms_descending_tree = {} for on_resource, tree in PERMISSIONS_INHERITANCE_TREE.items(): for obtained_perm, obtained_from in tree.items(): for from_resource, perms in obtained_from.items(): for perm in perms: perms_descending_tree.setdefault(from_resource, {}).setdefault( perm, {} ).setdefault(on_resource, set()).add(obtained_perm) # Obtain current principals. principals = self.request.prefixed_principals # Query every possible permission of the current user from backend. backend = self.request.registry.permission perms_by_object_uri = backend.get_accessible_objects(principals) # Check settings for every allowed resources. from_settings = allowed_from_settings(self.request.registry.settings, principals) # Add additional resources and permissions defined in settings/plugins for root_perm in from_settings.get("root", []): resource_name, _ = root_perm.split(":") perms_by_object_uri.setdefault("/", set()).add(root_perm) perms_descending_tree.setdefault("root", {}).update({root_perm: {"root": {root_perm}}}) # Expand permissions obtained from backend with the object URIs that # correspond to permissions allowed from settings. allowed_resources = {"bucket", "collection", "group"} & set(from_settings.keys()) if allowed_resources: storage = self.request.registry.storage every_bucket, _ = storage.get_all(parent_id="", collection_id="bucket") for bucket in every_bucket: bucket_uri = "/buckets/{id}".format_map(bucket) for res in allowed_resources: resource_perms = from_settings[res] # Bucket is always fetched. if res == "bucket": perms_by_object_uri.setdefault(bucket_uri, set()).update(resource_perms) continue # Fetch bucket collections and groups. # XXX: wrong approach: query in a loop! every_subobjects, _ = storage.get_all(parent_id=bucket_uri, collection_id=res) for subobject in every_subobjects: subobj_uri = bucket_uri + "/{0}s/{1}".format(res, subobject["id"]) perms_by_object_uri.setdefault(subobj_uri, set()).update(resource_perms) entries = [] for object_uri, perms in perms_by_object_uri.items(): try: # Obtain associated res from object URI resource_name, matchdict = core_utils.view_lookup(self.request, object_uri) except ValueError: # Skip permissions entries that are not linked to an object URI continue # For consistency with event payloads, if resource has an id, # prefix it with its resource name if "id" in matchdict: matchdict[resource_name + "_id"] = matchdict["id"] # The imaginary "root" resource gets mapped to the hello # view. Handle it explicitly. if resource_name == "hello": resource_name = "root" # Expand implicit permissions using descending tree. permissions = set(perms) for perm in perms: obtained = perms_descending_tree[resource_name][perm] # Related to same resource only and not every sub-objects. # (e.g "bucket:write" gives "bucket:read" but not "group:read") permissions |= obtained[resource_name] entry = dict( uri=object_uri, resource_name=resource_name, permissions=list(permissions), **matchdict ) entries.append(entry) return extract_record_set( entries, filters=filters, sorting=sorting, id_field="uri", pagination_rules=pagination_rules, limit=limit, )
def get_objects( self, filters=None, sorting=None, pagination_rules=None, limit=None, include_deleted=False, parent_id=None, ): # Invert the permissions inheritance tree. perms_descending_tree = {} for on_resource, tree in PERMISSIONS_INHERITANCE_TREE.items(): for obtained_perm, obtained_from in tree.items(): for from_resource, perms in obtained_from.items(): for perm in perms: perms_descending_tree.setdefault( from_resource, {}).setdefault(perm, {}).setdefault( on_resource, set()).add(obtained_perm) # Obtain current principals. principals = self.request.prefixed_principals # Query every possible permission of the current user from backend. backend = self.request.registry.permission perms_by_object_uri = backend.get_accessible_objects(principals) # Check settings for every allowed resources. from_settings = allowed_from_settings(self.request.registry.settings, principals) # Add additional resources and permissions defined in settings/plugins for root_perm in from_settings.get("root", []): resource_name, _ = root_perm.split(":") perms_by_object_uri.setdefault("/", set()).add(root_perm) perms_descending_tree.setdefault("root", {}).update( {root_perm: { "root": {root_perm} }}) # Expand permissions obtained from backend with the object URIs that # correspond to permissions allowed from settings. allowed_resources = {"bucket", "collection", "group"} & set( from_settings.keys()) if allowed_resources: storage = self.request.registry.storage every_bucket, _ = storage.get_all(parent_id="", resource_name="bucket") for bucket in every_bucket: bucket_uri = "/buckets/{id}".format_map(bucket) for res in allowed_resources: resource_perms = from_settings[res] # Bucket is always fetched. if res == "bucket": perms_by_object_uri.setdefault( bucket_uri, set()).update(resource_perms) continue # Fetch bucket collections and groups. # XXX: wrong approach: query in a loop! every_subobjects, _ = storage.get_all(parent_id=bucket_uri, resource_name=res) for subobject in every_subobjects: subobj_uri = bucket_uri + f"/{res}s/{subobject['id']}" perms_by_object_uri.setdefault( subobj_uri, set()).update(resource_perms) entries = [] for object_uri, perms in perms_by_object_uri.items(): try: # Obtain associated res from object URI resource_name, matchdict = core_utils.view_lookup( self.request, object_uri) except ValueError: # Skip permissions entries that are not linked to an object URI continue # For consistency with event payloads, if resource has an id, # prefix it with its resource name if "id" in matchdict: matchdict[resource_name + "_id"] = matchdict["id"] # The imaginary "root" resource gets mapped to the hello # view. Handle it explicitly. if resource_name == "hello": resource_name = "root" # Expand implicit permissions using descending tree. permissions = set(perms) for perm in perms: obtained = perms_descending_tree[resource_name][perm] # Related to same resource only and not every sub-objects. # (e.g "bucket:write" gives "bucket:read" but not "group:read") permissions |= obtained[resource_name] entry = dict( uri=object_uri, resource_name=resource_name, permissions=list(permissions), **matchdict, ) entries.append(entry) return extract_object_set( entries, filters=filters, sorting=sorting, id_field="uri", pagination_rules=pagination_rules, limit=limit, )
def delete(self): principal = self.request.matchdict["principal"] storage = self.request.registry.storage permission = self.request.registry.permission object_uris_and_permissions = permission.get_accessible_objects( [principal]) object_uris = list(object_uris_and_permissions.keys()) write_perm_principals = permission.get_objects_permissions( object_uris, ["write"]) to_delete = set() for object_uri, principals in zip(object_uris, write_perm_principals): principals = principals["write"] # "Ownership" isn't a real concept in Kinto, so instead we # define ownership as meaning "this user is the only one # who can write to this object". if principals == set([principal]): to_delete.add(object_uri) # Any accessible objects that won't be deleted, need to have # the user's permission removed. for object_uri, permissions in object_uris_and_permissions.items(): if object_uri in to_delete: continue for perm in permissions: permission.remove_principal_from_ace(object_uri, perm, principal) to_delete = condense_under_parents(self.request, to_delete) # Group by (parent_uri, resource of child) to make fewer # requests to storage backend. # Store the parsed object IDs, since those are what we # actually give to the storage backend. object_ids_by_parent_uri = collections.defaultdict(list) # Store also the object URIs, which we give to the permission backend. objects_by_parent_uri = collections.defaultdict(list) # We have to get the matchdict of the child here anyhow, so # keep that to generate events later. matchdicts_by_parent_uri = {} for object_uri in to_delete: parent_uri = get_parent_uri(object_uri) resource_name, matchdict = core_utils.view_lookup( self.request, object_uri) objects_by_parent_uri[(parent_uri, resource_name)].append(object_uri) object_ids_by_parent_uri[(parent_uri, resource_name)].append(matchdict["id"]) # This overwrites previous matchdicts for the parent, but # we'll only use the fields that are relevant to the # parent, which will be the same for each child. matchdicts_by_parent_uri[parent_uri] = matchdict for (parent_uri, resource_name), object_ids in object_ids_by_parent_uri.items(): # Generate the parent matchdict from an arbitrary child's matchdict. matchdict = {**matchdicts_by_parent_uri[parent_uri]} matchdict.pop("id", None) # Deletes are paginated too, so take the page size from settings. batch_size = self.request.registry.settings[ "storage_max_fetch_size"] for batch in slice_into_batches(object_ids, batch_size): batch = list(batch) filters = [Filter("id", batch, core_utils.COMPARISON.IN)] timestamp = storage.resource_timestamp(resource_name, parent_uri) records, _ = storage.get_all(resource_name=resource_name, parent_id=parent_uri, filters=filters) tombstones = storage.delete_all(resource_name=resource_name, parent_id=parent_uri, filters=filters) notify_resource_event( self.request, parent_uri, timestamp, tombstones, ACTIONS.DELETE, old=records, resource_name=resource_name, resource_data=matchdict, ) # FIXME: need to purge the above tombstones, but no # way to purge just some tombstones for just this # principal # Clear permissions from the deleted objects, for # example those of other users. permission.delete_object_permissions( *objects_by_parent_uri[(parent_uri, resource_name)]) # Remove this principal from existing users. permission.remove_principal(principal) # Remove this principal from all groups that contain it. associated_principals = permission.get_user_principals(principal) for associated_principal in associated_principals: permission.remove_user_principal(principal, associated_principal) return {"data": {"principal": principal}}