Skip to content

Instantly share code, notes, and snippets.

@aregee
Forked from airtonix/account.api.mixins.py
Created August 22, 2013 18:13
Show Gist options
  • Save aregee/6310787 to your computer and use it in GitHub Desktop.
Save aregee/6310787 to your computer and use it in GitHub Desktop.
from django.http import HttpResponse
from tastypie import http
from tastypie.exceptions import ImmediateHttpResponse
from tastypie.resources import convert_post_to_put
class PublicEndpointResourceMixin(object):
""" Public endpoint dispatcher, for those routes upon which you don't want
to enforce the current resources authentication limits."""
def dispatch_public(self, request_type, request, **kwargs):
"""
Same as `tastypie.resources.Resource.dispatch` except that
we don't check if the user is authenticated
"""
allowed_methods = getattr(self._meta, "%s_allowed_methods" % request_type, None)
if 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META:
request.method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
request_method = self.method_check(request, allowed=allowed_methods)
method = getattr(self, "%s_%s" % (request_method, request_type), None)
if method is None:
raise ImmediateHttpResponse(response=http.HttpNotImplemented())
self.throttle_check(request)
# All clear. Process the request.
request = convert_post_to_put(request)
response = method(request, **kwargs)
# Add the throttled request.
self.log_throttled_access(request)
# If what comes back isn't a ``HttpResponse``, assume that the
# request was accepted and that some action occurred. This also
# prevents Django from freaking out.
if not isinstance(response, HttpResponse):
return http.HttpNoContent()
return response
from django.contrib.auth.models import User, Permission
from django.contrib.auth import login, logout, authenticate
from django.db.models import Q
from tastypie.http import HttpUnauthorized, HttpForbidden
from tastypie.resources import ModelResource
from tastypie import fields
from tastypie.utils import trailing_slash
from tastypie.authentication import MultiAuthentication, ApiKeyAuthentication, SessionAuthentication
from surlex.dj import surl
from waffle import sample_is_active
from waffle.models import Flag, Switch, Sample
from guardian.models import UserObjectPermission
# from base.api.cache import RedisCache
from .authorizations import UserOnlyAuthorization
from .mixins import PublicEndpointResourceMixin
class ProfileResource(ModelResource):
class Meta:
queryset = User.objects.all()
authentication = MultiAuthentication(ApiKeyAuthentication(), SessionAuthentication())
allowed_methods = ['get', ]
resource_name = 'profile'
class UserResource(PublicEndpointResourceMixin, ModelResource):
features = fields.DictField(blank=True, null=True, readonly=True)
apikey = fields.CharField(blank=True, null=True, readonly=True)
user_permissions = fields.ListField(blank=True, null=True, readonly=True)
class Meta:
queryset = User.objects.all()
authentication = MultiAuthentication(ApiKeyAuthentication(), SessionAuthentication())
authorization = UserOnlyAuthorization()
fields = ['pk', 'username', 'first_name', 'last_name', 'email', ]
allowed_methods = ['get', 'post']
login_allowed_methods = ['post', ]
resource_name = 'user'
urlargs = {"name": resource_name, "slash": trailing_slash()}
def dehydrate_user_permissions(self, bundle):
user = bundle.obj
user_app_permissions = user.user_permissions.all()
user_object_permissions = UserObjectPermission.objects.filter(user=user).distinct()
return list(user_app_permissions.values_list('codename', flat=True)) + list(user_object_permissions.values_list('permission__codename', flat=True))
def dehydrate_features(self, bundle):
user = bundle.obj
users_groups = user.groups.all()
find_everyone_flags = Q(everyone=True)
find_rollout_flags = Q(rollout=True)
find_authenticated_flags = Q(authenticated=True)
find_user_flags = Q(users__in=[user.id])
find_group_flags = Q(groups__in=users_groups)
enabled_flags = Flag.objects.filter(find_everyone_flags | find_rollout_flags | find_authenticated_flags | find_user_flags | find_group_flags)
enabled_switches = Switch.objects.filter(active=True)
enabled_samples = []
for sample in Sample.objects.all():
if sample_is_active(sample):
enabled_samples.append(sample.name)
flags = list(set(enabled_flags.values_list('name', flat=True)))
switches = list(set(enabled_switches.values_list('name', flat=True)))
samples = list(set(enabled_samples))
return {
"flags": flags,
"switches": switches,
"samples": samples,
"all": " ".join(flags + switches + samples)
}
def dehydrate_apikey(self, bundle):
user = bundle.obj
if hasattr(user, 'api_key') and user.api_key.key:
return user.api_key.key
return None
def prepend_urls(self):
return [
surl(r"^<resource_name={name}>/login{slash}$".format(**self._meta.urlargs), self.wrap_view('dispatch_login'), name='api_user_login'),
surl(r"^<resource_name={name}>/logout{slash}$".format(**self._meta.urlargs), self.wrap_view('dispatch_logout'), name='api_user_logout'),
]
def dispatch_login(self, request, **kwargs):
"""
A view for handling the various HTTP methods (GET/POST/PUT/DELETE) on
a single resource.
Relies on ``Resource.dispatch`` for the heavy-lifting.
"""
return self.dispatch_public('login', request, **kwargs)
def post_login(self, request, **kwargs):
data = self.deserialize(request, request.raw_post_data, format=request.META.get('CONTENT_TYPE', 'application/json'))
username = data.get('username', '')
password = data.get('password', '')
user = authenticate(username=username, password=password)
if user:
if user.is_active:
login(request, user)
return self.get_detail(request, pk=user.id)
else:
# Inactive User
return self.create_response(request, {'success': False, 'reason': 'disabled', }, HttpForbidden)
else:
# Incorrect Login
return self.create_response(request, {'success': False, 'reason': 'incorrect'}, HttpUnauthorized)
def dispatch_logout(self, request, **kwargs):
"""
A view for handling the various HTTP methods (GET/POST/PUT/DELETE) on
a single resource.
Relies on ``Resource.dispatch`` for the heavy-lifting.
"""
return self.dispatch('logout', request, **kwargs)
def get_logout(self, request, **kwargs):
if request.user and request.user.is_authenticated():
logout(request)
return self.create_response(request, {'success': True})
else:
# Not logged in
return self.create_response(request, {'success': False}, HttpUnauthorized)
EnabledResources = (
UserResource,
ProfileResource
)
(function () {
'use strict';
/*
Angular Apikey Session Authentication
This module deals with the following concepts
- anonymous / authenticated users
- username/password for initial session authentication
- apikeys instead of passwords for remaining interaction
- feature flip code checking
:: Dataflow
event:login-required
perform anything you need at this point, something like
showing a login form would be appropriate
LoginController.login
sends username and password via an event to the session service
$Session.login
performs the api post to the login endpoint
collects the user data and stores userid, apikey, username in time limited cookies
on success, it broadcasts a login-confirmed
:: Elements
Service: $Session
.hasFeatures
.logout
.login
.setApiKeyAuthHeader
.refreshCredentials
.cacheCredentials
.wipeCredentials
Controller: LoginController
.login
Run: Initialise Module
sets up all the events required to decouple this module.
event:auth-login
event:auth-logout
event:auth-login-required
event:auth-login-confirmed
$routeChangeSuccess
*/
angular.module("ClientApp.factories.session", [])
.constant('constSessionExpiry', 20) // in minutes
.factory("$Session", [
'$Application',
'$rootScope',
'$q',
'$location',
'$log',
'$http',
'Restangular',
'authService',
'constSessionExpiry',
function($Application, $rootScope, $q, $location, $log, $http, Restangular, authService, constSessionExpiry) {
return {
loginInProgress: false,
User: null,
hasFeatures: function(){
/*
Uses underscore _.difference to yield a list of feature
codes the user does not have.
returns:
false: if the user is missing feature codes,
true: if the user has all the requested feature codes
::arguments, array
list of feature codes you want to check for
*/
// bail out early
if(!this.User || !this.User.features) return false;
var userCodeList = this.User.features.all.split(" ");
return _.difference(arguments, userCodeList).length == 0
},
authSuccess: function(){
this.loginInProgress = false;
$rootScope.$broadcast('event:session-changed');
authService.loginConfirmed();
},
logout: function(){
$log.info("Handling request for logout");
this.wipeUser();
$rootScope.$broadcast('event:auth-logout-confirmed');
},
login: function(data){
$log.info("Preparing Login Data", data);
var $this = this;
return Restangular
.all('user/login/')
.post(data)
.then(function userLoginSuccess(response){
$log.info("login.post: auth-success", response);
$this.User = response;
// remove properties we don't need.
delete $this.User.route
delete $this.User.restangularCollection
$this.User.is_authenticated = true;
$this.cacheUser()
$this.setApiKeyAuthHeader();
$this.authSuccess();
}, function userLoginFailed(response){
$log.info('login.post: auth-failed', response);
$this.logout();
return $q.reject(response);
});
},
setApiKeyAuthHeader: function(){
if(this.hasOwnProperty('User') && this.User){
$http.defaults.headers.common.Authorization = "apikey "+this.User.username+':'+this.User.apikey;
$log.info("Setting Authorization Header", $http.defaults.headers.common.Authorization)
}else{
$log.info("No user for AuthHeader")
delete $http.defaults.headers.common.Authorization;
}
},
refreshUser: function(){
var $this = this;
var cachedUser = lscache.get('userData');
$log.info("Request to pull User from Cache");
$log.info("$Session.User", $this.User)
$log.info('lscache.get("userData")', cachedUser)
if(!$this.User && cachedUser && cachedUser.hasOwnProperty('apikey') && cachedUser.apikey){
$log.info('Attempting pull user from cache', cachedUser)
$this.User = cachedUser;
}else{
$log.warn("No user available.")
$rootScope.$broadcast("event:auth-login-required")
}
if($this.User && $this.User.hasOwnProperty('apikey') && $this.User.apikey){
$this.setApiKeyAuthHeader();
Restangular
.one('user', $this.User.id)
.get().then(function(response){
$log.info("User data updated from server.")
$this.User = response;
$this.cacheUser();
$this.setApiKeyAuthHeader();
$this.authSuccess()
}, function(response){
$log.error("Error retrieving user. logging out.");
$this.logout();
})
}
},
cacheUser: function(){
if(!this.User){
$log.warn("Can't cache a null value User")
return false;
}
if(!this.User.hasOwnProperty("id") && this.User.hasOwnProperty("resource_uri")){
$log.info("Building $this.User.id")
var bits = this.User.resource_uri.split("/")
this.User.id = Number(bits[bits.length-1])
}
$log.info("Caching User", this.User);
lscache.set('userData', this.User, constSessionExpiry);
},
wipeUser: function(){
$log.info("Wiping User");
lscache.remove('userData');
this.User = null;
this.setApiKeyAuthHeader();
$rootScope.$broadcast('event:session-changed');
}
};
}]).
controller("LoginController", function($log, $Session, $scope, $rootScope){
$scope.Login = function(){
$scope.$emit('event:auth-login', {username: $scope.username, password: $scope.password});
}
}).
run(['$rootScope',
'$log',
'$Session',
function($rootScope, $log, $Session){
$rootScope.Session = $Session;
//namespace the localstorage with the current domain name.
lscache.setBucket(window.location.hostname);
// on page refresh, ensure we have a user. if none exists
// then auth-login-required will be triggered.
$Session.refreshUser();
// Best practice would be to hook these events in your app.config
// login
$rootScope.$on('event:auth-login-required', function(scope, data) {
$log.info("session.login-required");
});
$rootScope.$on('event:auth-login', function(scope, data) {
$log.info("session.send-login-details");
$Session.login(data);
});
$rootScope.$on('event:auth-login-confirmed', function(scope, data) {
$log.info("session.login-confirmed");
});
// logout
$rootScope.$on('event:auth-logout', function(scope, data) {
$log.info("session.request-logout");
$Session.logout();
});
$rootScope.$on('event:auth-logout-confirmed', function(scope, data) {
$log.info("session.logout-confirmed");
});
// session state change
$rootScope.$on('event:session-changed', function(scope){
$log.info("session.changed > ", $Session.User)
})
$rootScope.$on('$routeChangeSuccess', function(event, next, current) {
if(!$Session.User && next.$$route.loginRequired){
$log.info("Unauthenticated access to ", next.$$route)
$rootScope.$broadcast('event:auth-login-required')
}
});
}])
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment