-
-
Save aregee/6310787 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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