Code Monkey home page Code Monkey logo

django-scim2's People

Contributors

andersk avatar caleb15 avatar dbaxa avatar dependabot[bot] avatar erikvanzijst avatar fabaff avatar horida avatar logston avatar omidraha avatar pablodiazgutierrez avatar powellc avatar robertpark avatar stefanfoulis avatar timmytango avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-scim2's Issues

Unable to Decouple the User Model

I have requirements such that I am not allowed to do any migrations to my default user model, so I extended my user model to a new SCIM_USER_MODEL by considering User model with one to one relation. now in settings file when I am mentioning the USER_MODEL_GETTER it is saying "Manager isn't accessible via SCIM_USER_MODEL instances". Please help me on this

Implement get_user_model same as get_group_model

Is your feature request related to a problem? Please describe.

Currently for detect user model, the django-scim2 use get_user_model from the Django builtin function,
And get_user_model of Django, use settings.AUTH_USER_MODEL

    user = get_user_model().objects.get(id=1)
    scim_user = SCIMUser(user)

But if you want to use another defined model for user of scim that is related to the customized user model of settings.AUTH_USER_MODEL , there is no settings for doing that. we should override all related files as urls, views, filter and adapter.

Describe the solution you'd like

Same as get_user_model that get group model from the scim_settings settings:

def get_group_model():
    """
    Return the group model.
    """
    return scim_settings.GROUP_MODEL
SCIM_SERVICE_PROVIDER = {
      'GROUP_MODEL': 'accounts.models.Group',
}

Implement it as:

def get_user_model():
    """
    Return the user model.
    """
    return scim_settings.USER_MODEL
SCIM_SERVICE_PROVIDER = {
      'USER_MODEL': 'accounts.models.UserSCIM',
}

Describe alternatives you've considered

Override model_cls_getter of UsersView and UserFilterQuery, and SCIMUser.handle_add `

Additional context

externalId is not stored when creating a user and not returned when retrieving a user

Describe the bug
externalId is not stored in User when creating a new user via POST method and not returned when retrieving a user via GET.

To Reproduce
curl -H "Authorization: Bearer xxxxx" "http://localhost:8000/scim/v2/Users" -d '{"schemas":["urn:ietf:params:scim:schemas:core:2.0:User","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"],"externalId":"0a21f0f2-8d2a-4f8e-bf98-7363c4aed4ef","userName":"Test_User_ab6490ee-1e48-479e-a20b-2d77186b5dd1","active":true,"emails":[{"primary":true,"type":"work","value":"[email protected]"}],"meta":{"resourceType":"User"},"name":{"formatted":"givenName familyName","familyName":"familyName","givenName":"givenName"},"roles":[]}' -X POST

curl -H "Authorization: Bearer xxxxxx" "http://localhost:8000/scim/v2/Users/14"

Expected behavior
externalId should be stored in model and returned when retrieving a user.

[feature] post created user

After the user created, I want to put some log for tracking

I wrote something like this

class PostView(object):
    def post(self, request, **kwargs):
        obj = self.model_cls()
        scim_obj = self.scim_adapter(obj, request=request)
        body = json.loads(request.body.decode(constants.ENCODING))
        scim_obj.from_dict(body)

        try:
            scim_obj.save()
            if self.post_post:                 #
                self.post_post(scim_obj) #

Cannot resolve keyword 'scim_id' into field

Describe the bug
When doing an API call to GET /scim/v2/Users, getting a Cannot resolve keyword 'scim_id' into field.
Looks like the user model does not go through the SCIMUSer adapter. any ideas?

Using
python 3.7
django 2.1.7

Stacktrace

2019-06-05T10:18:43 local app: ERROR /venv/app/lib/python3.7/site-packages/django_scim/views.py func:dispatch line:94 - Unable to complete SCIM call.
Traceback (most recent call last):
File "/venv/app/lib/python3.7/site-packages/django_scim/views.py", line 91, in dispatch
return super(SCIMView, self).dispatch(request, *args, **kwargs)
File "/venv/app/lib/python3.7/site-packages/django/views/generic/base.py", line 88, in dispatch
return handler(request, *args, **kwargs)
File "/venv/app/lib/python3.7/site-packages/django_scim/views.py", line 257, in get
return self.get_many(request)
File "/venv/app/lib/python3.7/site-packages/django_scim/views.py", line 281, in get_many
return self._build_response(request, qs, *self._page(request))
File "/venv/app/lib/python3.7/site-packages/django_scim/views.py", line 201, in _build_response
total_count = sum(1 for _ in qs)
File "/venv/app/lib/python3.7/site-packages/django/db/models/query.py", line 268, in iter
self._fetch_all()
File "/venv/app/lib/python3.7/site-packages/django/db/models/query.py", line 1186, in _fetch_all
self._result_cache = list(self._iterable_class(self))
File "/venv/app/lib/python3.7/site-packages/django/db/models/query.py", line 54, in iter
results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
File "/venv/app/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 1052, in execute_sql
sql, params = self.as_sql()
File "/venv/app/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 449, in as_sql
extra_select, order_by, group_by = self.pre_sql_setup()
File "/venv/app/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 51, in pre_sql_setup
order_by = self.get_order_by()
File "/venv/app/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 315, in get_order_by
field, self.query.get_meta(), default_order=asc))
File "/venv/app/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 668, in find_ordering_name
field, targets, alias, joins, path, opts, transform_function = self._setup_joins(pieces, opts, alias)
File "/venv/app/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 698, in _setup_joins
field, targets, opts, joins, path, transform_function = self.query.setup_joins(pieces, opts, alias)
File "/venv/app/lib/python3.7/site-packages/django/db/models/sql/query.py", line 1473, in setup_joins
names[:pivot], opts, allow_many, fail_on_missing=True,
File "/venv/app/lib/python3.7/site-packages/django/db/models/sql/query.py", line 1389, in names_to_path
"Choices are: %s" % (name, ", ".join(available)))
django.core.exceptions.FieldError: Cannot resolve keyword 'scim_id' into field. Choices are: auth_token, date_joined, email, first_name, groups, id, is_active, is_staff, is_superuser, last_login, last_name, logentry, oauth2_provider_accesstoken, oauth2_provider_application, oauth2_provider_grant, oauth2_provider_refreshtoken, password, user_permissions, user_settings, username, appcomments, appuser
2019-06-05T10:18:43 local app: ERROR /venv/app/lib/python3.7/site-packages/django/utils/log.py func:log_response line:228 - Internal Server Error: /api/scim/v2/Users
[05/Jun/2019 10:18:44] "GET /api/scim/v2/Users HTTP/1.1" 500 289

request.user.is_anonymous is not method in Django 2.x.

Describe the bug
The following error occurred when accessing a SCIM API in Django 2.x.

Internal Server Error: /
Traceback (most recent call last):
  File "/home/tomatsue/.local/lib/python3.6/site-packages/django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
  File "/home/tomatsue/.local/lib/python3.6/site-packages/django_scim/middleware.py", line 20, in __call__
    self.process_request(request)
  File "/home/tomatsue/.local/lib/python3.6/site-packages/django_scim/middleware.py", line 27, in process_request
    if not hasattr(request, 'user') or request.user.is_anonymous():
TypeError: 'bool' object is not callable

A bool value is called here.

if not hasattr(request, 'user') or request.user.is_anonymous():

According to Django 1.10: django.contrib.auth.models.User.is_anonymous, is_anonymous is no longer a method.

Changed in Django 1.10:
In older versions, this was a method. Backwards-compatibility support for using it as a method will be removed in Django 2.0.

To Reproduce
Run curl -v localhost:8000/scim/v2/curl in Django 2.x.

Expected behavior
Check if the user is anonymous without the error.

Stacktrace
Described in above Describe the bug.

Additional context
Nothing.

0.11.1 on pypi is missing the schemas (project can't start)

Describe the bug
Project startup with 0.11.1 fails with FileNotFoundError: [Errno 2] No such file or directory: '/virtualenv/lib/python3.6/site-packages/django_scim/schemas/core' error.

0.11.0 works.

To Reproduce
Install version 0.11.1 from pypi, follow setup instructions and try running django.

Expected behavior
Starts without errors.

Stacktrace

Traceback (most recent call last):
  File "src/manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/virtualenv/lib/python3.6/site-packages/django/core/management/__init__.py", line 364, in execute_from_command_line
    utility.execute()
  File "/virtualenv/lib/python3.6/site-packages/django/core/management/__init__.py", line 356, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/virtualenv/lib/python3.6/site-packages/django/core/management/base.py", line 283, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/virtualenv/lib/python3.6/site-packages/django/core/management/base.py", line 327, in execute
    self.check()
  File "/virtualenv/lib/python3.6/site-packages/django/core/management/base.py", line 359, in check
    include_deployment_checks=include_deployment_checks,
  File "/virtualenv/lib/python3.6/site-packages/django/core/management/commands/migrate.py", line 62, in _run_checks
    issues.extend(super(Command, self)._run_checks(**kwargs))
  File "/virtualenv/lib/python3.6/site-packages/django/core/management/base.py", line 346, in _run_checks
    return checks.run_checks(**kwargs)
  File "/virtualenv/lib/python3.6/site-packages/django/core/checks/registry.py", line 81, in run_checks
    new_errors = check(app_configs=app_configs)
  File "/virtualenv/lib/python3.6/site-packages/django/core/checks/urls.py", line 16, in check_url_config
    return check_resolver(resolver)
  File "/virtualenv/lib/python3.6/site-packages/django/core/checks/urls.py", line 26, in check_resolver
    return check_method()
  File "/virtualenv/lib/python3.6/site-packages/django/urls/resolvers.py", line 256, in check
    for pattern in self.url_patterns:
  File "/virtualenv/lib/python3.6/site-packages/django/utils/functional.py", line 35, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/virtualenv/lib/python3.6/site-packages/django/urls/resolvers.py", line 407, in url_patterns
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
  File "/virtualenv/lib/python3.6/site-packages/django/utils/functional.py", line 35, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/virtualenv/lib/python3.6/site-packages/django/urls/resolvers.py", line 400, in urlconf_module
    return import_module(self.urlconf_name)
  File "/virtualenv/lib/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 678, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/myproject/urls.py", line 133, in <module>
    url(r'^scim/v2/', include('django_scim.urls', namespace='scim')),
  File "/virtualenv/lib/python3.6/site-packages/django/conf/urls/__init__.py", line 50, in include
    urlconf_module = import_module(urlconf_module)
  File "/virtualenv/lib/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 678, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/virtualenv/lib/python3.6/site-packages/django_scim/urls.py", line 6, in <module>
    from . import views
  File "/virtualenv/lib/python3.6/site-packages/django_scim/views.py", line 423, in <module>
    class SchemasView(SCIMView):
  File "/virtualenv/lib/python3.6/site-packages/django_scim/views.py", line 427, in SchemasView
    schemas_by_uri = {s['id']: s for s in get_all_schemas_getter()()}
  File "/virtualenv/lib/python3.6/site-packages/django_scim/utils.py", line 63, in get_all_schemas_getter
    return scim_settings.SCHEMAS_GETTER
  File "/virtualenv/lib/python3.6/site-packages/django_scim/settings.py", line 120, in __getattr__
    val = perform_import(val, attr)
  File "/virtualenv/lib/python3.6/site-packages/django_scim/settings.py", line 73, in perform_import
    return import_from_string(val, setting_name)
  File "/virtualenv/lib/python3.6/site-packages/django_scim/settings.py", line 86, in import_from_string
    module = importlib.import_module(module_path)
  File "/virtualenv/lib/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "/virtualenv/lib/python3.6/site-packages/django_scim/schemas/__init__.py", line 22, in <module>
    ALL = load_schemas()
  File "/virtualenv/lib/python3.6/site-packages/django_scim/schemas/__init__.py", line 13, in load_schemas
    files = os.listdir(sub_dir)
FileNotFoundError: [Errno 2] No such file or directory: '/virtualenv/lib/python3.6/site-packages/django_scim/schemas/core'

Additional context
This is probably due to the static files for the schemas missing in the packaged version. Probably since the move of code into the src sub-directory. The path needs to be changed in MANIFEST.in as well.

Fixed in #28

Use the django ORM for filters instead of a custom SQL query generator

While trying to use django-scim2 in a project, I noticed, that the default UserFilterQuery uses a custom SQL query building class under the hood. This is also the case for the default GroupFilterParser. It could also very well be that I overlooked something.

Anyway, I tried to work around this by using a custom filter parsers, would like to share what I tried, and could currently see no better option than filing an issue here πŸ™‚.

The custom parsers look something like this (everything that was needed seemed to be already there in the scim2_filter_parser):

from scim2_filter_parser.lexer import SCIMLexer
from scim2_filter_parser.parser import SCIMParser
from scim2_filter_parser.transpilers.django_q_object import Transpiler as SCIMTranspiler

class CustomFilterParserBase:
    attr_map = {}

    @classmethod
    def search(cls, query, request=None):
        return cls.query_to_qs(query, cls.attr_map)

    @staticmethod
    def query_to_qs(query, attr_map):
        tokens = SCIMLexer().tokenize(query)
        ast = SCIMParser().parse(tokens)
        qs = SCIMTranspiler(attr_map).transpile(ast)
        return qs


class UserFilterParser(CustomFilterParserBase):
    attr_map = {
        # attr, sub attr, uri
        ("externalId", None, None): "userlicense__external_user_id",
        ("userName", None, None): "username",
        ("name", "familyName", None): "last_name",
        ("familyName", None, None): "last_name",
        ("name", "givenName", None): "first_name",
        ("givenName", None, None): "first_name",
        ("active", None, None): "is_active",
    }


class GroupFilterParser(CustomFilterParserBase):
    attr_map = {
        # attr, sub attr, uri
        ("externalId", None, None): "external_group_id",
        ("displayName", None, None): "name",
        # ("userName", None, None): "username",
        # ("name", "familyName", None): "last_name",
        # ("familyName", None, None): "last_name",
        # ("name", "givenName", None): "first_name",
        # ("givenName", None, None): "first_name",
        # ("active", None, None): "is_active",
    }

I set them in the SCIM_SERVICE_PROVIDER configuration within settings.py like this:

SCIM_SERVICE_PROVIDER = {
    # ...
    "USER_FILTER_PARSER": "my_scim.filters.UserFilterParser",
    "GROUP_FILTER_PARSER": "my_scim.filters.GroupFilterParser",
    # ...
}

And I have a custom UsersView and GroupsView that look something like this:

import json
from django_scim import constants
from django_scim import exceptions as scim_exceptions
from django_scim import views
from scim2_filter_parser.parser import SCIMParserError

class CustomFilterMixin:
    def _search(self, request, query, start, count):
        qs = self.model_cls.objects.all()

        try:
            q = self.__class__.parser_getter().search(query, request)
        except (ValueError, SCIMParserError) as e:
            raise scim_exceptions.BadRequestError(
                "Invalid filter/search query: " + str(e)
            )
        else:
            qs = qs.filter(q)

        extra_filter_kwargs = self.get_extra_filter_kwargs(request)
        qs = qs.filter(**extra_filter_kwargs)

        extra_exclude_kwargs = self.get_extra_exclude_kwargs(request)
        qs = qs.exclude(**extra_exclude_kwargs)

        qs = self.get_queryset_post_processor(request, qs)
        qs = qs.order_by(self.lookup_field)

        return self._build_response(request, qs, *self._page(request))

class UsersView(CustomFilterMixin, views.UsersView):
    pass

class GroupsView(CustomFilterMixin, views.GroupsView):
    pass

I hope that this can be of use and If you have any more question, please let me know.

Has anyone used this with MySQL?

I tried fixing a few things and running on our MySQL implementation, but I'm getting a is an invalid keyword argument for this function error that's stumping me at the moment, and I wonder if this is a real limitation or just a "we never tried that" situation.

Missing information on how to use the filter

Going over the code, there seems to be no support for the .search format as defined in http://www.simplecloud.info/. Instead, we need to POST for example to /Groups/.search which is ok, but there's no documentation on the expected format. We got as far as figuring out that this should work:

{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:SearchRequest"], "filter": "displayName eq \"group-name\"" }

...but alas I get a blank list as my only result. Is there some documentation about this aspect, or at least a sample query that we can use to get started?

(And thanks for this terrific piece of software!)

PATCH request to update the user fails

Describe the bug
PATCH request to update user attributes results in 500 error.

To Reproduce
curl -H "Authorization: Bearer xxxxxx" "http://localhost:8000/scim/v2/Users/14" -d '{"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],"Operations": [{"op": "Replace","path": "name.familyName","value": "updatedFamilyName"}]}' -X PATCH

Expected behavior
User's attribute should be updated and user should be returned.

Stacktrace

Traceback (most recent call last):
  File "/xxxxxxxxxx/django_scim/views.py", line 91, in dispatch
    return super(SCIMView, self).dispatch(request, *args, **kwargs)
  File "/xxxxxxxxxx/django/views/generic/base.py", line 88, in dispatch
    return handler(request, *args, **kwargs)
  File "/xxxxxxxxxx/django_scim/views.py", line 336, in patch
    scim_obj.handle_operations(operations)
  File "/xxxxxxxxxx/django_scim/adapters.py", line 87, in handle_operations
    handler(path, value, operation)
  File "/xxxxxxxxxx/django_scim/adapters.py", line 270, in handle_replace
    for attr, attr_value in attrs.items():
AttributeError: 'unicode' object has no attribute 'items'

admin's scim_id was changed.

I built the environment as follows.

  • SCIM ServiceProvider with django-scim2
  • createsuperuser admin
  • implement oauth bearer-token (access-token) with django-oauth-toolkit

Doing some tests with curl, I was able to get information of ServiceProviderConfig, ResourceTypes, and Schemas with the GET method.
(I also confirmed that errors occur when there is no bearer token.)

However, when I tried to create a User with POST, scim_id was duplicated.
So I overwrite scim_id in models.py to comment-out unique=True (change to unique=False), I was able to create a user.

I soon found out why this happened.

Attempting to get the user with scim_id=21 with GET /scim/v2/Users/21 returned an error that multiple users were detected.
And looking at admin's scim_id, it changed to 21.

I changed admin's scim_id to 1 and tried creating another user again, and admin's scim_id is changed to 22 which is the same as the new user.

Considering the timing, I think that there is a problem with save (L368) of PostView Class in views.py below:
https://github.com/15five/django-scim2/blob/master/src/django_scim/views.py#L368

I overwrite "save" method by referring to "demo/app/adapters.py".


def save(self):
    is_new_user = self.is_new_user
    try:
        with transaction.atomic():
            super().save()
            if is_new_user:
                # Set SCIM ID to be equal to database ID. Because users are uniquely identified with this value
                # its critical that changes to this line are well considered before executed.
                self.obj.__class__.objects.update(scim_id=str(self.obj.id))
            logger.info(f'User saved. User id {self.obj.id}')
    except Exception as e:
        # raise self.reformat_exception(e)
        raise RuntimeError(e.args) from e

I think that there is a problem with the below URL.

https://github.com/15five/django-scim2/blob/master/src/django_scim/models.py#L150-L153

Or is there a problem with the save overriding or the method of issuing Bearer Tokens?

PATCH not working for boolean values

Describe the bug
when using PATCH to update a record it fails with NotImplementedError when updating a boolean field.

To Reproduce
Try to PATCH a User Resource with this:

{
	"schemas":
		["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
	"Operations": [{
		"op":"replace",
		"path":"active",
		"value": true
	}]
}

Expected behaviour
Can alter fields with boolean values.

Additional context
I suppose we should support bool (and int, float ?) here and here

Support SCIM views with custom authentication mechanisms

login_requried was added to SCIMView.dispatch in f791a44, marking all requests to the SCIM views required for authentication.

This ties with authentication based on the request.user object added by Django's AuthenticationMiddleware, therefore requiring us to actually have a User object representing an authenticated client.

This is not necessarily the case when there isn't a corresponding AUTH_USER_MODEL that can be associated with the SCIM client. login_required ensures the presence of user which is_authenticated, but it does not have anything to do directly with the views themselves.

A current workaround is to create a class preventing to be AUTH_USER_MODEL, that implements is_authenticated. This breaks type-safety and Django's expectation for request.user. But it would be better if django-scim2 actually supports custom authentication methods for the SCIM views.

Additional context can be found here.

Incompatibility with django 4

Describe the bug
django-scim2 is incompatible with django v4.0 which was released Dec 7, 2021.

The code in src/django_scim/models.py is using the API ugettext_lazy (see line 5).
This API was deprecated in django v3.0 (Release notes) and should be replaced with gettext_lazy().

The same function is used in demo/app/models.py in line 4.

Sorting based on modified field not supported in get_many method

Describe the bug

The code sorts the results based on the lookup_field ,
and there is no way to sort the results based on any other field, especially the modified field.

    def get_many(self, request):
        query = request.GET.get('filter')
        if query:
            return self._search(request, query, *self._page(request))

        extra_filter_kwargs = self.get_extra_filter_kwargs(request)
        extra_exclude_kwargs = self.get_extra_exclude_kwargs(request)
        qs = self.model_cls.objects.filter(
            **extra_filter_kwargs
        ).exclude(
            **extra_exclude_kwargs
        )
        qs = self.get_queryset_post_processor(request, qs)
        qs = qs.order_by(self.lookup_field)
        return self._build_response(request, qs, *self._page(request))

To Reproduce
Steps to reproduce the behavior...

Expected behavior
We expect to be able to sort the results based on any field, especially the modified field.

Stacktrace

Additional context

By swapping these two lines of code, the ordering of the queryset by lookup_field will be performed before applying any custom filtering or modifications via get_queryset_post_processor(),

So the solution is to replace the following code:

qs = self.get_queryset_post_processor(request, qs)
qs = qs.order_by(self.lookup_field)

with this code:

qs = qs.order_by(self.lookup_field)
qs = self.get_queryset_post_processor(request, qs)

`handle_replace` explicitly calls `self.obj.save()`

Describe the bug
adapters.py:SCIMUser.handle_replace (and SCIMGroup as well) explicitly calls self.obj.save() instead of the SCIMUser.save() (SCIMGroup.save() respectively)

Expected behavior
After calling the handle_operations in views.py:PatchView.patch call the save() method of SCIMUser (SCIMGroup).

Additional context
I need to perform checks and execute code whenever a user object is saved.

500 error when loading /Users sometime after release 0.8.2

I just updated from 0.5.* to the latest 0.10.6 and noticed I consistently get a 500 error () when loading /Users and /Groups:

unbound method get_user_model() must be called with UsersView instance as first argument (got nothing instead)

We use Python 2.7 and Django 1.11, and at no point the stack trace shows our internal code:

ERROR 2019-04-14 13:26:16,237 views 31136 139791130908416 Unable to complete SCIM call.
Traceback (most recent call last):
File "[...]/site-packages/django_scim/views.py", line 90, in dispatch
return super(SCIMView, self).dispatch(request, *args, **kwargs)
File "[...]/site-packages/django/views/generic/base.py", line 88, in dispatch
return handler(request, *args, **kwargs)
File "[...]/site-packages/django_scim/views.py", line 246, in get
return self.get_many(request)
File "[...]/site-packages/django_scim/views.py", line 262, in get_many
extra_filter_kwargs = self.get_extra_filter_kwargs(request)
File "[...]/site-packages/django_scim/views.py", line 47, in get_extra_filter_kwargs
return get_extra_model_filter_kwargs_getter(self.model_cls)
File "[...]/site-packages/django_scim/views.py", line 43, in model_cls
return self.class.model_cls_getter()
TypeError: unbound method get_user_model() must be called with UsersView instance as first argument (got nothing instead)
[2019/04/14 13:26:16] HTTP GET /scim/v2/Users 500 [0.34, 127.0.0.1:42578]

Is this perhaps an issue for Python2 only?

Decouple User model from SCIM attributes?

Is your feature request related to a problem? Please describe.
I'm delighted to find django-scim2 and am excited to use it to connect to AzureAD.

I am not sure how to fix this error: AttributeError: 'User' object has no attribute 'scim_id'.

I don't think I can copy the User implementation in the demo:

class User(AbstractSCIMUser, TimeStampedModel, AbstractBaseUser):

as django documentation warns against implementing a custom user model mid project and I haven't implemented a custom user model from the beginning.

Describe the solution you'd like
I'd like to implement a 'profile style' model to store my scim related data in, like so:

class UserScim(models.Model):
	user = models.OneToOneField(User, on_delete=models.CASCADE, related_name = 'user_scim')
	scim_id = models.CharField(
		_('SCIM ID'),
		max_length=254,
		null=True,
		blank=True,
		default=None,
		unique=True,
		help_text=_('A unique identifier for a SCIM resource as defined by the service provider.'),
	)
        scim_external_id = models.CharField(
                _('SCIM External ID'),
                max_length=254,
                null=True,
                blank=True,
                default=None,
                db_index=True,
                help_text=_('A string that is an identifier for the resource as defined by the provisioning client.'),
        )
	def __str__(self):
		return self.user.username + ' ' + str(self.scim_id)

Would this decoupling work? What would I need to update elsewhere?

Thank you very much.

Filters don't apply to reverse foreign keys.

The get_nested_field() function by @pablodiazgtierrez is a really nice addition to this library, but doesn't seem to work for reverse relationships.

I have the same use case as Pablo (needing to filter by company in a multitennant SAAS). However in my use case the object relationship direction of the object is a reverse one, (Example Model below).

I'm using this line in get_extra_filter_kwargs:

def get_extra_filter_kwargs(request, *args, **kwargs):
            return {
                'companies__company': request.user.memberships.first().company,
            }

Similar to Pablo's original issue, for requests to /Users?filter=userName+eq+"example_username" the result is blank, but /Users/1 and Users?attributes=userName do work correctly, and apply the company restrictions as expected.

I think I've diagnosed that the hasattr() line (shown below) that was added in this merge is causing this the filtering to stop prematurely.

            if not hasattr(obj, field_name):
                return None

Example Model

class UserInCompany(models.Model):
        company = models.ForeignKey(Company, related_name = 'company_members', on_delete=models.CASCADE,  null=True, blank=True)
	user = models.ForeignKey(User, on_delete=models.CASCADE, related_name = 'memberships', null=True, blank=True)
class Company(models.Model):
        company_name = models.CharField(max_length=200, null=True, blank=True)

Originally posted by @William-Wildridge in #48 (comment)

Not syncing the group members

Hi,

When I do the provisioning from my AD the group members are not been saved. I get all the groups and users but the groups have no users in it.

What's the extent to which filtering is supported?

First of all, thank you for putting out this excellent library. We are so glad to have it around. We are using it to interact with Microsoft's AAD user provisioning system, and so far it has served us well.

Now, we've heard from Microsoft staff that they're adding some tests and that we're failing them. Specifically, they ask why when they query /Users?filter=userName+eq+"[email protected]" the result is blank (even though they can query /Users/USERID successfully for the same user).

I will point to them that, as explained in /ServiceProviderConfig, filtering is not supported. However, in the code it says that there's partial support. So I'm wondering:

  • What's the extent of this support?

  • How can we help extend this support to include the query above?

Edit: I should have labeled this issue a "question" or "help wanted", but I didn't see the option. Sorry!

Re-creating of users

I was wondering if there is a way to handle re-creation of users.
When a user gets created, removed (set to inactive), and created again, django will throw the exception that the user already exists. I`m not sure what is the safest way to implement that.

Also, is there a way to limit which user (type) can execute the scim api calls?

And thanks for the library, it saves me tons of work!

Requests are only approved when a cookie-header is provided

Hi,

I'm not able to access the SCIM-endpoints while using a Bearer token (OAuth 2) or using Basic Authentication, while all my other endpoints (other than SCIM) do allow this.
I've figured out that my requests to the SCIM endpoints do succeed when I provide a Cookie-header in my request, with references to the csrf-token and sessionid. If I don't do this, I always get a 401 - Unauthorized, even if I provide a Bearer token or login credentials.

I have included your middleware after the authentication-middleware as instructed in your documentation, but I can't seem to use the SCIM-endpoints without providing a Cookie-header in my request.
Is this expected behavior or did I configure something wrong?

This is a portion of my settings.py file:

`INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'users',
'api',
'corsheaders',
'django_filters',
'django_scim',
'oauth2_provider'
]

SCIM_SERVICE_PROVIDER = {
'NETLOC': 'localhost',
'AUTHENTICATION_SCHEMES': [
{
'type': 'oauth2',
'name': 'OAuth 2',
'description': 'Oauth 2 implemented with bearer token',
'primary': 'true'
},
{
"name": "HTTP Basic",
"description": "Authentication scheme using the HTTP Basic Standard",
"type": "httpbasic"
}
],
}

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django_scim.middleware.SCIMAuthCheckMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
]

REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
'rest_framework.authentication.BasicAuthentication',
),
}`

Why is `get_queryset_post_processor` not applied to querysets in `_search`?

get_queryset_post_processor is not applied to querysets in _search. Not sure if this is a bug or there is some reason for this, but it seems odd that the queryset_post_processor is applied to the queryset for list requests, but not when a filter is passed.

E.g., I have a queryset_post_processor which returns qs.filter(username__startswith='test'), and my queryset consists of two users, id 1 has username testuser and id 2 has username nottestuser. If I make a GET /Users request, only user 1 is returned. However, if I make a GET /Users/?filter=userName eq "nottestuser", user 2 is returned. I expected the behaviour to be for the 2nd request to not return any result, as filtering the request should return a subset of the results obtained in the request with no filters.

I receive a 501 - Not Implemented, even though the user is successfully created in my DB

Hi,

I'm implementing your package into my project and I can add users to my database using the scim/v2/Users endpoint. However, each time I send a POST-request, the user does get created in my database but I receive a 501 status-code, instead of a 201 - Created status.
I also get the same status code whenever I try to retrieve a specific user with its ID.
Can't seem to figure out why this is happening.

This is an example of the message in the request's body:

{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"userName": "[email protected]",
"password": "test123",
"emails": [
{
"value": "[email protected]",
"type": "work"
}
],
"externalId": "abanderas",
"name": {
"familyName": "Banderas",
"givenName": "Antonio"
}
}

Move to Pytest

Really need to move to pytest; so that usage of things like coverage (amongst other things) has a greater level of community support.

ModuleNotFoundError: No module named 'django_scim'

Describe the bug
ModuleNotFoundError: No module named 'django_scim'

To Reproduce
Add django_scim Django (cookiecibtter.

Expected behavior
django_scim2 module to be found and docker to complete spin-up.

Stacktrace
Screenshot 2022-10-28 at 2 47 56 AM

Additional context
Screenshot 2022-10-28 at 2 56 07 AM

Screenshot 2022-10-28 at 2 59 45 AM

> $ pip list -V | grep django-scim2                                                                                                 [Β±main ●]
django-scim2             0.17.3

Addded django_scim2 to requirements.txt

django-scim2==0.17.3  # https://github.com/15five/django-scim2

ATTR_MAP hard-linked to UserFilterQuery ignoring USER_FILTER_PARSER setting

Describe the bug
In the adapter the ATTR_MAP is set to the attr_map of the UserFilterQuery objects:

ATTR_MAP = filters.UserFilterQuery.attr_map

There is however a setting where the UserFilterQuery object can be overwritten by a custom object.

Expected behavior
The object specified in the USER_FILTER_PARSER setting should be used. This can be achieved by using utils.get_user_filter_parser (see here).

Unauthorized issues

I am getting the unauthorized issues on scim endpoints I know authentication and authorization is not implemented on django-scim. That is why I integrated Django OAuth Toolkit.
But it didn't work out. I tested demo project, but I am getting the same issue.

Describe the bug
Unauthorized: /scim/v2/Users
"GET /scim/v2/Users HTTP/1.1" 401 0

Expected behavior
I want to retrieve /scim endpoint with Bearer token.

SCIMView.dispatch() should allow opting out of putting all exceptions' details in the response

The SCIMView.dispatch method:

    @method_decorator(csrf_exempt)
    @method_decorator(login_required)
    def dispatch(self, request, *args, **kwargs):
        if not self.implemented:
            return self.status_501(request, *args, **kwargs)

        try:
            return super(SCIMView, self).dispatch(request, *args, **kwargs)
        except Exception as e:
            if not isinstance(e, exceptions.SCIMException):
                logger.exception('Unable to complete SCIM call.')
                e = exceptions.SCIMException(str(e))

            content = json.dumps(e.to_dict())
            return HttpResponse(content=content,
                                content_type=constants.SCIM_CONTENT_TYPE,
                                status=e.status)

converts any exception into a string and puts it in the HttpResponse. I think ideally there'd be a setting allowing library users to opt out of this behavior (or possibly even better - this behavior should be disabled by default and require explicitly enabling it) and use a generic message such as Exception occurred while processing the SCIM request. The reason being that while this is very useful for debugging, it can be dangerous in production - sensitive information can be revealed through str(e) and the SCIM client may not always be a party so trusted that revealing information would be harmless.

As a simple example if you deploy some bug to production where some_dict[secret_string] gets called causing KeyError, the secret_string will be revealed to the SCIM client.

Unable to install v0.16.4 via pip

Describe the bug
PYPI describes the latest version as 0.16.4 but unable to install it.

To Reproduce

pip install django-scim2==0.16.4

Expected behavior

  • 0.16.4 installs correctly

Stacktrace

Collecting django-scim2==0.16.4
  Using cached django_scim2-0.16.3-py3-none-any.whl
ERROR: Requested django-scim2==0.16.4 from file:///Users/myuser/Library/Caches/pip/wheels/93/3f/bf/0a26b7ee52fda2c188c8d752d3d7611a6ff375c84b9d0deb01/django_scim2-0.16.3-py3-none-any.whl (from -r requirements.txt (line 64)) has different version in metadata: '0.16.3'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.