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"
|
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
| 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
| 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
| 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
|