User Management - adaptive-learning/flocs GitHub Wiki
User Management
There are, in some sense, four different types of users in the system.
Model name | Project | What is does |
---|---|---|
User | Django | Default basic user representation. |
AnonymousUser | Django | Represents a user that is not authenticated in any way. Cannot be used to track single real user. |
LazyUser | django-lazysignup | Similar to AnonymousUser, but with the possibility to keep track of the user. |
UserSocialAuth | Python Social Auth | Represents user authenticated through 3rd parties like Google and Facebook. |
In the frontend, there is only one representation of user. It is based on User model and it is extended with two extra fields to store information about lazy user and social user.
Where to get a user instance
User and AnonymousUser are passed as user
attribute of HTTP request received in Django views. Since AnonymousUser implements User interface, it is safe to use all User methods and access User attributes. Both LazyUser and UserSocialAuth require an instance of User that is associated to them. Therefore, you can access LazyUser or UserSocialAuth by supplying associated User instance from request.
Enforcing user authentication and authorization
Do not forget to enforce authentication and authorization on both frontend and backend.
Backend
To enforce users to be authenticated and have proper authorization, you can use custom decorators @login_required
and @staff_member_required
in your views. If you want to allow lazy users see lazy signup section.
@login_required
decorator requires a user to be properly authenticated in the system. Right now, that can be either through username and password or through 3rd party providers, Google and Facebook. Not even lazy user are not sufficient. The following snippet shows usage of this decorator.
from user.decorators import login_required
@login_required
def get_student_statistics(request):
# code in this view can safely assume there is a user logged in
your code here...
@staff_member_required
decorator requires a user to be set as active and also requires a user to be member of staff (having is_staff
attribute set to True
). However, it does not explicitly disallow unauthenticated users form accessing the given view. If there happens to be a lazy user, who is also a member of staff, then he satisfies all requirements placed by this decorator. The snippet below shows example usage of this decorator.
from user.decorators import staff_member_required
@staff_member_required
def get_admin_stats(request):
# code in this view can safely assume there is a staff member accessing this view
your code here...
Frontend
There is custom accessService with checks for user authentication and permissions. These methods return promise that is resolved in case the condition is satisfied or rejected otherwise. To use accessService first define a state for the URL you wish to be protected. Then add resolve
property (see $stateProvider) with some of the accessService methods. The available methods are following.
isUserAvailable
requires user to be logged in or to be a lazy userisAuthenticated
requires user to be logged in, lazy user is not enough. Unfortunately, user object's fieldauthenticated
in both backend and frontend is set to True even for lazy user. This makes things a bit messy.isStaff
requires user to be logged in and to have staff privileges.
The enforcing itself is done by listening to $stateChangeError
events. In case of authentication, current behaviour is to display login modal. If the modal is dismissed (no login made) 401 error page is displayed. For authorisation violation the 403 error page is displayed right away.
The following example will require a user to be fully authenticated (lazy user is not enough) before accessing the URL /profile
.
.state('profile', {
url: '/profile',
templateUrl: 'profile/profile.tpl.html',
controller: 'profileCtrl',
data: {
titleTraslationKey: 'PROFILE'
},
resolve: {
access: function(accessService) {
return accessService.isAuthenticated();
},
}
})
Lazy Signup
Lazy signup allows to abstract from authenticated and hopefully-soon-signed-up users. As an example where is this behaviour useful, consider the practice session: we want a user to start the practice without forcing them to sign up before, but we also want to record their progress and save it if they sign up after/during the practice session.
We are using django-lazysignup package.
When you want to abstract some controller (Django view) from lazy users, use @allow_lazy_user
annotation.
If the current user hasn't been authenticated yet, this will create a (temporary) account for them and so allows you to perform any operations with the current user as if they are normal authenticated users.
Example:
## practice/views.py ##
from lazysignup.decorators import allow_lazy_user
@allow_lazy_user
def get_next_task(request):
task = practice_service.get_next_task(student=request.user)
return JsonResponse(task)
If you need to know, if the current user is lazy or not, you can use lazysignup.utils.is_lazy_user
function (ant there is also @require_nonlazy_user
annotation).
Social user
Social users are those authenticated through 3rd party provides of OAuth, currently Google and Facebook. Social users are somewhat similar to lazy users in implementation. They are stored independently and they are associated to exactly one Django User instance. Usually, you will only have to deal with associated Django user and not social users them selves.
To have social login available, it is necessary to set up application credentials for both Google and Facebook. These credentials are application key and application secret. Both can be found in administration pages, https://console.developers.google.com for Google and https://developers.facebook.com/apps for Facebook. These credentials then has to be inserted in private settings file (see Private Settings).
Google project settings
The project has to have Google+ API enabled and has to have authorised redirect URIs set. The redirect URI is of form protocol://hostname/social/complete/google-oauth2/
. Note that all the hosts that the application is going to be deployed at has to be entered.
Facebook app settings
The project has to be set as public, is has to have Facebook login 'product' added and all the valid OAuth redirect URIs has to be set in Facebook login settings. The redirect URI is of form protocol://hostname/social/complete/facebook/
. Note that all the hosts that the application is going to be deployed at has to be entered.
Frontend user object
Current user is stored in a user
variable of userService
. It is a JSON object with the following structure.
var user = {
username: "",
firstName: "",
lastName: "",
authenticated: false,
isLazyUser: false,
email: "",
isStaff: false,
providers: [],
};
username
field contains either user selected name during sign up, concatenation of first and last name (Facebook login) or part of the email address before @ symbol (Google login). firstName
and lastName
are either blank in case of standard sign up or filled with details provided by 3rd party (Google and Facebook). Field authenticated
contains information whether the user is somehow authenticated. That means being logged in or being lazy user. Basically, whether or not there is a Django User entry for this user. isLazyUser
field tells whether the user is lazy user or not. Field email
is self explanatory. isStaff
field declares whether or not the user has staff privileges, i.e. can access admin statistics. providers
field contains list of 3rd party authentication providers for this user. It can be empty list in case the user has signed up in our system. In some cases, if the user has same email for both Facebook and Google account, it may contain multiple providers.