Exemplo n.º 1
0
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
Exemplo n.º 2
0
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}
Exemplo n.º 3
0
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
Exemplo n.º 4
0
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
Exemplo n.º 5
0
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}
Exemplo n.º 6
0
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
Exemplo n.º 7
0
    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)
Exemplo n.º 8
0
    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,
        )
Exemplo n.º 9
0
    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,
        )
Exemplo n.º 10
0
    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}}