Skip to content

Login#

This app handles the user authentication using django allauth and implements a user profile that provide an overview on tables and helps to manage the datasets draft or published state. Additionally the profile pages include the permission groups to manage data table resource access permissions as group with other users. The last feature is the user profile, including a view showing the api token with functionality to reset it as well as a Form to provide additional user data like adding a user image.

Setup#

First make sure to install django allauth package and install it into the existing project:

  • install latest requirements.txt in the python environment

    pip install -r requirements.txt

  • run python migrations to setup the new django allauth models (tables)

    python manage.py migrate

  • check your iptables setting on the server to enable server to server connection using the service static ip address. Don`t forget to restart the iptables service to apply the updates.

Now edit your securitysettings.py and update it with the content form the securitysettings.py.default template file to setup the social provider used for 3rd Party Login flow. We use openIDConnect that is implemented by django allauth:

Note

Filling out the values in the dictionary depends on your Provider. They should provide documentation or provide you with the relevant credentials. In some cases the provider_id must be in line with the specification of the provider in others you can choose your own name here. The client_id & secret should also be provided as well as the server_url.

SOCIALACCOUNT_PROVIDERS = {
    "openid_connect": {
        # For each OAuth based provider, either add a ``SocialApp``
        # (``socialaccount`` app) containing the required client
        # credentials, or list them here:
        "APPS": [{
            "provider_id": "",
            "name": "",
            "client_id": "",
            "secret": "",
            "VERIFIED_EMAIL": True,
            "EMAIL_AUTHENTICATION": True,
            "settings": {"server_url": ""},
        }]
    }
}

App Components#

The components of each app implement the django app structure and implement a MVVM pattern for web applications. This includes the files model.py, views.py, urls.py, Then there are migrations that specify the django table structure and is also a core django feature. The templates include all HTML page payouts including django template syntax to render pages with dynamic server data and JavaScript. Additionally there might be other folders and python modules available.w

Views#

ProfileUpdateView

Bases: UpdateView, LoginRequiredMixin

Autogenerate a update form for users.

Source code in login/views.py
814
815
816
817
818
819
820
821
class ProfileUpdateView(UpdateView, LoginRequiredMixin):
    """
    Autogenerate a update form for users.
    """

    model = OepUser
    fields = ["name", "affiliation", "email"]
    template_name_suffix = "_update_form"

Forms#

3rd party Signup

Bases: SignupForm

Renders the form when user has signed up using social accounts. Default fields will be added automatically. See UserSignupForm otherwise.

Source code in login/forms.py
17
18
19
20
21
22
class UserSocialSignupForm(SocialSignupForm):
    """
    Renders the form when user has signed up using social accounts.
    Default fields will be added automatically.
    See UserSignupForm otherwise.
    """

Default Signup

Bases: SignupForm

Source code in login/forms.py
25
26
27
28
29
30
class CreateUserForm(SignupForm):
    captcha = CaptchaField()

    def save(self, request):
        user = super(CreateUserForm, self).save(request)
        return user

Edit existing user data

Bases: UserChangeForm

A form for updating users. Includes all the fields on the user, but replaces the password field with admin's password hash display field.

Source code in login/forms.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class EditUserForm(UserChangeForm):
    """A form for updating users. Includes all the fields on
    the user, but replaces the password field with admin's
    password hash display field.
    """

    # do NOT show the password field in the form
    password = None

    class Meta:
        model = OepUser
        fields = (
            "profile_img",
            # "email",
            "fullname",
            "location",
            "work",
            "linkedin",
            "twitter",
            "facebook",
            "affiliation",
            "description",
        )

    def __init__(self, *args, **kwargs):
        super(UserChangeForm, self).__init__(*args, **kwargs)
        for key in self.Meta.fields:
            field = self.fields[key]
            cstring = field.widget.attrs.get("class", "")
            field.widget.attrs["class"] = cstring + "form-control"
            if field.required:
                field.label_suffix = "*"

    def clean_password(self):
        return None

Adapters#

AccountAdapter #

Bases: DefaultAccountAdapter

Handles default logins

Source code in login/adapters.py
16
17
18
19
20
21
22
class AccountAdapter(DefaultAccountAdapter):
    """
    Handles default logins
    """

    def is_open_for_signup(self, request: HttpRequest) -> bool:
        return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)

SocialAccountAdapter #

Bases: DefaultSocialAccountAdapter

Handles logins via 3rd party organizations like ORCID.

Source code in login/adapters.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class SocialAccountAdapter(DefaultSocialAccountAdapter):
    """
    Handles logins via 3rd party organizations like ORCID.
    """

    def is_open_for_signup(
        self, request: HttpRequest, sociallogin: SocialLogin
    ) -> bool:
        return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)

    def populate_user(
        self,
        request: HttpRequest,
        sociallogin: SocialLogin,
        data: dict[str, typing.Any],
    ) -> User:
        """
        Populates user information from social provider info.

        See: https://django-allauth.readthedocs.io/en/latest/advanced.html?#creating-and-populating-user-instances # noqa
        """
        provider = sociallogin.account.provider

        # Specific modifications for the RegApp context data.
        # Provider name must be the same as in securitysettings.
        if provider == "RegApp":
            name = data.get(
                "name"
            )  # NOTE: Consider to add random user name if not available
            first_name = data.get("given_name")
            last_name = data.get("given_name")
            new_data = data
            new_data["username"] = name
            new_data["first_name"] = first_name
            new_data["last_name"] = last_name

        return super().populate_user(request, sociallogin, data)

populate_user(request, sociallogin, data) #

Populates user information from social provider info.

See: https://django-allauth.readthedocs.io/en/latest/advanced.html?#creating-and-populating-user-instances # noqa

Source code in login/adapters.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def populate_user(
    self,
    request: HttpRequest,
    sociallogin: SocialLogin,
    data: dict[str, typing.Any],
) -> User:
    """
    Populates user information from social provider info.

    See: https://django-allauth.readthedocs.io/en/latest/advanced.html?#creating-and-populating-user-instances # noqa
    """
    provider = sociallogin.account.provider

    # Specific modifications for the RegApp context data.
    # Provider name must be the same as in securitysettings.
    if provider == "RegApp":
        name = data.get(
            "name"
        )  # NOTE: Consider to add random user name if not available
        first_name = data.get("given_name")
        last_name = data.get("given_name")
        new_data = data
        new_data["username"] = name
        new_data["first_name"] = first_name
        new_data["last_name"] = last_name

    return super().populate_user(request, sociallogin, data)

Models#

The user manager that handles oeplatform users and their system role

Bases: UserManager

Source code in login/models.py
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
class OEPUserManager(UserManager):
    def create_user(
        self,
        name,
        email,
        affiliation=None,
        profile_img=None,
        registration_date=None,
        fullname=None,
        linkedin=None,
        facebook=None,
        twitter=None,
        location=None,
        work=None,
    ):
        if not email:
            raise ValueError("An email address must be entered")
        if not name:
            raise ValueError("A name must be entered")

        user = self.model(
            name=name,
            email=self.normalize_email(email),
            affiliation=affiliation,
            profile_img=profile_img,
            registration_date=registration_date,
            fullname=fullname,
            linkedin=linkedin,
            facebook=facebook,
            twitter=twitter,
            location=location,
            work=work,
        )

        user.save(using=self._db)
        return user

    def create_superuser(
        self,
        name,
        email,
        affiliation,
        profile_img,
        registration_date,
        fullname,
        linkedin,
        facebook,
        twitter,
        location,
        work,
    ):
        user = self.create_user(
            name,
            email,
            affiliation=affiliation,
            profile_img=profile_img,
            registration_date=registration_date,
            fullname=fullname,
            linkedin=linkedin,
            facebook=facebook,
            twitter=twitter,
            location=location,
            work=work,
        )
        user.is_admin = True
        user.save(using=self._db)
        return user

    def create_devuser(self, name, email):
        if not email:
            raise ValueError("An email address must be entered")
        if not name:
            raise ValueError("A name must be entered")
        user = self.model(
            name=name,
            email=self.normalize_email(email),
            affiliation=name,
        )
        user.save(using=self._db)
        return user

The user model of a oeplatform user

Bases: AbstractBaseUser, PermissionHolder

Source code in login/models.py
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
class myuser(AbstractBaseUser, PermissionHolder):
    name = models.CharField(max_length=50, unique=True, verbose_name="Username")
    affiliation = models.CharField(max_length=50, blank=True)
    email = models.EmailField(verbose_name="email address", max_length=255, unique=True)
    profile_img = models.ImageField(null=True, blank=True)
    registration_date = models.DateTimeField(auto_now_add=True)
    fullname = models.CharField(
        max_length=50, null=True, blank=True, verbose_name="Full Name"
    )
    work = models.CharField(max_length=50, null=True, blank=True)
    facebook = models.URLField(max_length=500, blank=True, null=True)
    linkedin = models.URLField(max_length=500, blank=True, null=True)
    twitter = models.URLField(max_length=500, blank=True, null=True)
    location = models.CharField(max_length=50, blank=True, null=True)

    did_agree = models.BooleanField(default=False)

    is_active = models.BooleanField(default=True)

    ###################################################################
    is_mail_verified = models.BooleanField(default=False)  # TODO: remove
    ###################################################################

    is_admin = models.BooleanField(default=False)

    is_native = models.BooleanField(default=True)

    description = models.TextField(blank=True)

    USERNAME_FIELD = "name"

    REQUIRED_FIELDS = [name]

    objects = OEPUserManager()

    def get_full_name(self):
        return self.name

    def get_short_name(self):
        return self.name

    def __str__(self):  # __unicode__ on Python 2
        return self.name

    @property
    def is_staff(self):
        return self.is_admin

    def get_table_memberships(self):
        direct_memberships = self.table_permissions.all().prefetch_related("table")
        return direct_memberships

    def get_table_permission_level(self, table):
        # Check admin permissions for user
        if self.is_admin:
            return ADMIN_PERM

        user_membership = self.table_permissions.filter(table=table).first()

        permission_level = NO_PERM
        if user_membership:
            permission_level = max(user_membership.level, permission_level)

        # Check permissions of all groups and choose least restrictive one
        group_perm_levels = (
            membership.group.get_table_permission_level(table)
            for membership in self.memberships.all()
        )

        if group_perm_levels:
            permission_level = max(
                itertools.chain([permission_level], group_perm_levels)
            )

        return permission_level