Skip to content

Instantly share code, notes, and snippets.

@jproby
Created February 11, 2014 21:21
Show Gist options
  • Save jproby/8944448 to your computer and use it in GitHub Desktop.
Save jproby/8944448 to your computer and use it in GitHub Desktop.
# API authentication
from social.apps.django_app.utils import strategy
from rest_framework.authtoken.models import Token
from rest_framework.views import APIView
from rest_framework import parsers
from rest_framework import renderers
from rest_framework.authentication import get_authorization_header
from rest_framework.response import Response
from rest_framework import status
from rest_framework.authtoken.serializers import AuthTokenSerializer
@strategy()
def register_by_access_token(request, backend):
backend = request.strategy.backend
# Split by spaces and get the array
auth = get_authorization_header(request).split()
if not auth or auth[0].lower() != b'token':
msg = 'No token header provided.'
return msg
if len(auth) == 1:
msg = 'Invalid token header. No credentials provided.'
return msg
access_token = auth[1]
user = backend.do_auth(access_token)
return user
# Pour une vraie integration au rest framework
class ObtainAuthToken(APIView):
throttle_classes = ()
permission_classes = ()
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
serializer_class = AuthTokenSerializer
model = Token
# Accepte un backend en parametre : 'auth' pour un login / pass classique
def post(self, request, backend):
serializer = self.serializer_class(data=request.DATA)
if backend == 'auth':
if serializer.is_valid():
token, created = Token.objects.get_or_create(user=serializer.object['user'])
return Response({'token': token.key})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
user = register_by_access_token(request, backend)
if user and user.is_active:
token, created = Token.objects.get_or_create(user=user)
return Response({'id': user.id, 'name': user.username, 'firstname': user.first_name, 'userRole': 'user', 'token': token.key})
class ObtainUser(APIView):
throttle_classes = ()
permission_classes = ()
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
serializer_class = AuthTokenSerializer
model = Token
# Renvoi le user si le token est valide
def get(self, request):
serializer = self.serializer_class(data=request.DATA)
if request.META.get('HTTP_AUTHORIZATION'):
auth = request.META.get('HTTP_AUTHORIZATION').split()
if not auth or auth[0].lower() != b'token' or len(auth) != 2:
msg = 'Invalid token header. No credentials provided.'
return Response(msg, status=status.HTTP_401_UNAUTHORIZED)
token = Token.objects.get(key=auth[1])
if token and token.user.is_active:
return Response({'id': token.user_id, 'name': token.user.username, 'firstname': token.user.first_name, 'userRole': 'user', 'token': token.key})
else:
return Response(serializer.errors, status=status.HTTP_401_UNAUTHORIZED)
class ObtainLogout(APIView):
throttle_classes = ()
permission_classes = ()
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
serializer_class = AuthTokenSerializer
model = Token
# Logout le user
def get(self, request):
return Response({'User': ''})
angular.module('myApp', [
// login service
'loginService',
// components
'ngAnimate',
'restangular'
])
.config(function ($urlRouterProvider, RestangularProvider) {
RestangularProvider.setBaseUrl('http://127.0.0.1:8000/api');
RestangularProvider.setRequestSuffix('/');
$urlRouterProvider.otherwise('/discover');
})
.run(function ($rootScope) {
/**
* $rootScope.doingResolve is a flag useful to display a spinner on changing states.
* Some states may require remote data so it will take awhile to load.
*/
var resolveDone = function () { $rootScope.doingResolve = false; };
$rootScope.doingResolve = false;
$rootScope.$on('$stateChangeStart', function () {
$rootScope.doingResolve = true;
});
$rootScope.$on('$stateChangeSuccess', resolveDone);
$rootScope.$on('$stateChangeError', resolveDone);
$rootScope.$on('$permissionError', resolveDone);
})
.controller('AppCtrl', function ($scope, $state, $stateParams, loginService, $http) {
// Expose $state and $stateParams to the <body> tag
$scope.$state = $state;
$scope.$stateParams = $stateParams;
// loginService exposed and a new Object containing login user/pwd
$scope.ls = loginService;
$scope.login = {
working: false
};
$scope.loginBK = function (backend) {
if (backend == 'facebook') {
OAuth.popup('facebook', function(error, success) {
if (error) {
}
else {
var token = "Token " + success.access_token
var loginPromise = $http({method:'POST', url: 'http://127.0.0.1:8000/api-token/login/' + backend + '/', headers: {'Authorization': token}});
$scope.login.working = true;
loginService.loginUser(loginPromise);
loginPromise.success(function () {
$scope.login = { working: false };
});
loginPromise.finally(function () {
$scope.login.working = false;
});
}
});
}
};
$scope.logoutMe = function () {
loginService.logoutUser($http.get('http://127.0.0.1:8000/api-token/logout'));
};
// Changement du dynamique du titre
$scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){
if ( angular.isDefined( toState.data.pageTitle ) ) {
$scope.pageTitle = toState.data.pageTitle + ' | Welcome' ;
}
});
// initilization de oauth.io
OAuth.initialize('your oauth.io key');
});
# -*- coding: UTF-8 -*-
"""Common settings and globals."""
from os.path import abspath, basename, dirname, join, normpath
from sys import path
from django.utils.translation import gettext_lazy as _
########## PATH CONFIGURATION
# Absolute filesystem path to the Django project directory:
DJANGO_ROOT = dirname(dirname(abspath(__file__)))
# Absolute filesystem path to the top-level project folder:
SITE_ROOT = dirname(DJANGO_ROOT)
# Site name:
SITE_NAME = basename(DJANGO_ROOT)
# Add our project to our pythonpath, this way we don't need to type our project
# name in our dotted import paths:
path.append(DJANGO_ROOT)
########## END PATH CONFIGURATION
########## DEBUG CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug
DEBUG = False
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug
TEMPLATE_DEBUG = DEBUG
########## END DEBUG CONFIGURATION
########## MANAGER CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#admins
ADMINS = (
('Your Name', '[email protected]'),
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#managers
MANAGERS = ADMINS
########## END MANAGER CONFIGURATION
########## DATABASE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.',
'NAME': '',
'USER': '',
'PASSWORD': '',
'HOST': '',
'PORT': '',
}
}
########## END DATABASE CONFIGURATION
########## GENERAL CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#time-zone
TIME_ZONE = 'Europe/Paris'
# See: https://docs.djangoproject.com/en/dev/ref/settings/#language-code
LANGUAGE_CODE = 'fr'
# See: https://docs.djangoproject.com/en/dev/ref/settings/#site-id
SITE_ID = 1
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n
USE_I18N = True
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n
USE_L10N = True
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
USE_TZ = True
LANGUAGES = (
('fr', _('Français')),
('en', _('Anglais')),
)
DEFAULT_LANGUAGE = 1
########## END GENERAL CONFIGURATION
########## MEDIA CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root
MEDIA_ROOT = normpath(join(SITE_ROOT, 'media'))
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url
MEDIA_URL = '/media/'
########## END MEDIA CONFIGURATION
########## STATIC FILE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root
STATIC_ROOT = normpath(join(SITE_ROOT, 'assets'))
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url
STATIC_URL = '/static/'
# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
STATICFILES_DIRS = (
normpath(join(SITE_ROOT, 'static')),
)
# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
)
########## END STATIC FILE CONFIGURATION
########## SECRET CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
# Note: This key only used for development and testing.
SECRET_KEY = r"secret"
########## END SECRET CONFIGURATION
########## FIXTURE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FIXTURE_DIRS
FIXTURE_DIRS = (
normpath(join(SITE_ROOT, 'fixtures')),
)
########## END FIXTURE CONFIGURATION
########## TEMPLATE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors
TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.core.context_processors.static',
'django.core.context_processors.tz',
'django.contrib.messages.context_processors.messages',
'django.core.context_processors.request',
'social.apps.django_app.context_processors.backends',
'social.apps.django_app.context_processors.login_redirect',
'account.context_processors.account',
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
TEMPLATE_DIRS = (
normpath(join(SITE_ROOT, 'templates')),
)
########## END TEMPLATE CONFIGURATION
########## MIDDLEWARE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#middleware-classes
MIDDLEWARE_CLASSES = (
# Default Django middleware.
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.locale.LocaleMiddleware',
'corsheaders.middleware.CorsMiddleware',
'social.apps.django_app.middleware.SocialAuthExceptionMiddleware',
"account.middleware.TimezoneMiddleware"
)
########## END MIDDLEWARE CONFIGURATION
########## URL CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf
ROOT_URLCONF = '%s.urls' % SITE_NAME
########## END URL CONFIGURATION
########## APP CONFIGURATION
INSTALLED_APPS = (
# Default Django apps:
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.comments',
# Useful template tags:
# 'django.contrib.humanize',
# Admin panel and documentation:
'django.contrib.admin',
# 3rd party direct social auth
'social.apps.django_app.default',
#'pinax_theme_bootstrap_account',
#'pinax_theme_bootstrap',
# Users Account
'account',
'avatar',
# rest framework et oauth for rest API
#'provider',
#'provider.oauth2',
'rest_framework',
'rest_framework.authtoken',
'corsheaders',
# data storage and db utils
'south', 'storages',
# External apps
'qhonuskan_votes', 'taggit',
)
# INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
########## END APP CONFIGURATION
########## LOGGING CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#logging
# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
# the site admins on every HTTP 500 error when DEBUG=False.
# See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse'
}
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}
########## END LOGGING CONFIGURATION
########## REST CONF
REST_FRAMEWORK = {
# Use hyperlinked styles by default.
# Only used if the `serializer_class` attribute is not set on a view.
'DEFAULT_MODEL_SERIALIZER_CLASS':
'rest_framework.serializers.HyperlinkedModelSerializer',
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
]
}
########## END REST CONF
########## WSGI CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application
WSGI_APPLICATION = 'wsgi.application'
########## END WSGI CONFIGURATION
########## Auth
AUTHENTICATION_BACKENDS = (
'social.backends.google.GoogleOAuth2',
'social.backends.twitter.TwitterOAuth',
'social.backends.facebook.FacebookOAuth2',
'django.contrib.auth.backends.ModelBackend'
)
ACCOUNT_EMAIL_UNIQUE = True
ACCOUNT_EMAIL_CONFIRMATION_REQUIRED = True
SOCIAL_AUTH_PIPELINE = (
'social.pipeline.social_auth.social_details',
'social.pipeline.social_auth.social_uid',
'social.pipeline.social_auth.auth_allowed',
'social.pipeline.social_auth.social_user',
'social.pipeline.user.get_username',
'social.pipeline.social_auth.associate_by_email',
'social.pipeline.user.create_user',
'social.pipeline.social_auth.associate_user',
'social.pipeline.social_auth.load_extra_data',
'social.pipeline.user.user_details'
)
CORS_ORIGIN_ALLOW_ALL = True
#CORS_URLS_REGEX = r'^/api/.*$'
#CORS_ALLOW_HEADERS = (
# 'x-requested-with',
# 'content-type',
# 'accept',
# 'origin',
# 'authorization',
# 'x-csrftoken',
# 'x-token'
# )
SOCIAL_AUTH_FACEBOOK_KEY = 'your_key_here'
SOCIAL_AUTH_FACEBOOK_SECRET = 'your_secret_here'
SOCIAL_AUTH_FACEBOOK_SCOPE = ['email', 'user_about_me', 'user_birthday', 'user_location']
########## End Auth
angular.module('loginService', [])
.provider('loginService', function () {
var userToken = localStorage.getItem('userToken'),
errorState = 'app.error',
logoutState = 'app.discover';
this.$get = function ($rootScope, $http, $q, $state, Restangular) {
/**
* Low-level, private functions.
*/
var setHeaders = function (token) {
if (!token) {
delete $http.defaults.headers.common['X-Token'];
return;
}
$http.defaults.headers.common['authorization'] = 'Token ' + token.toString();
Restangular.setDefaultHeaders({'authorization': 'Token ' + token.toString()});
};
var setToken = function (token) {
if (!token) {
localStorage.removeItem('userToken');
} else {
localStorage.setItem('userToken', token);
}
setHeaders(token);
};
var getLoginData = function () {
if (userToken) {
setHeaders(userToken);
} else {
wrappedService.userRole = userRoles.public;
wrappedService.isLogged = false;
wrappedService.doneLoading = true;
}
};
var managePermissions = function () {
// Register routing function.
$rootScope.$on('$stateChangeStart', function (event, to, toParams, from, fromParams) {
/**
* $stateChangeStart is a synchronous check to the accessLevels property
* if it's not set, it will setup a pendingStateChange and will let
* the grandfather resolve do his job.
*
* In short:
* If accessLevels is still undefined, it let the user change the state.
* Grandfather.resolve will either let the user in or reject the promise later!
*/
if (wrappedService.userRole === null) {
wrappedService.doneLoading = false;
wrappedService.pendingStateChange = {
to: to,
toParams: toParams
};
return;
}
// if the state has undefined accessLevel, anyone can access it.
// NOTE: if `wrappedService.userRole === undefined` means the service still doesn't know the user role,
// we need to rely on grandfather resolve, so we let the stateChange success, for now.
if (to.accessLevel === undefined || to.accessLevel.bitMask & wrappedService.userRole.bitMask) {
angular.noop(); // requested state can be transitioned to.
} else {
event.preventDefault();
// test this
$state.go(errorState, { error: 'unauthorized' }, { location: false, inherit: false });
$rootScope.$emit('$permissionError');
}
});
// Gets triggered when a resolve isn't fulfilled
// da aggiungere un caso in cui il resolve da informazioni solo ad un admin e non ad un user
// quindi un url ad esempio /resource/admin
// anche un url /resource/user
// in questo modo si potrà vedere l'error redirect!
// anche un caso in cui sono io che faccio fallire una $q cosi si vede l'errore stringa!
$rootScope.$on('$stateChangeError', function (event, to, toParams, from, fromParams, error) {
/**
* This is a very clever way to implement failure redirection.
* You can use the value of redirectMap, based on the value of the rejection
* So you can setup DIFFERENT redirections based on different promise errors.
*/
var errorObj, redirectObj;
// in case the promise given to resolve function is an $http request
// the error is a object containing the error and additional informations
error = (typeof error === 'object') ? error.status.toString() : error;
// in case of a random 4xx/5xx status code from server, user gets loggedout
// otherwise it *might* forever loop (look call diagram)
if (/^[45]\d{2}$/.test(error)) {
wrappedService.logoutUser();
}
/**
* Generic redirect handling.
* If a state transition has been prevented and it's not one of the 2 above errors, means it's a
* custom error in your application.
*
* redirectMap should be defined in the $state(s) that can generate transition errors.
*/
if (angular.isDefined(to.redirectMap) && angular.isDefined(to.redirectMap[error])) {
if (typeof to.redirectMap[error] === 'string') {
return $state.go(to.redirectMap[error], { error: error }, { location: false, inherit: false });
} else if (typeof to.redirectMap[error] === 'object') {
redirectObj = to.redirectMap[error];
return $state.go(redirectObj.state, { error: redirectObj.prefix + error }, { location: false, inherit: false });
}
}
return $state.go(errorState, { error: error }, { location: false, inherit: false });
});
};
/**
* High level, public methods
*/
var wrappedService = {
loginHandler: function (user, status, headers, config) {
/**
* Custom logic to manually set userRole goes here
*
* Commented example shows an userObj coming with a 'completed'
* property defining if the user has completed his registration process,
* validating his/her email or not.
*
* EXAMPLE:
* if (user.hasValidatedEmail) {
* wrappedService.userRole = userRoles.registered;
* } else {
* wrappedService.userRole = userRoles.invalidEmail;
* $state.go('app.nagscreen');
* }
*/
// setup token
setToken(user.token);
// update user
angular.extend(wrappedService.user, user);
// flag true on isLogged
wrappedService.isLogged = true;
// update userRole
wrappedService.userRole = userRoles[user.userRole];
return user;
},
loginUser: function (httpPromise) {
httpPromise.success(this.loginHandler);
},
logoutUser: function (httpPromise) {
/**
* De-registers the userToken remotely
* then clears the loginService as it was on startup
*/
setToken(null);
this.userRole = userRoles.public;
this.user = {};
this.isLogged = false;
$state.go(logoutState);
},
resolvePendingState: function (httpPromise) {
var checkUser = $q.defer(),
self = this,
pendingState = self.pendingStateChange;
// When the $http is done, we register the http result into loginHandler, `data` parameter goes into loginService.loginHandler
httpPromise.success(self.loginHandler);
httpPromise.then(
function success(httpObj) {
self.doneLoading = true;
// duplicated logic from $stateChangeStart, slightly different, now we surely have the userRole informations.
if (pendingState.to.accessLevel === undefined || pendingState.to.accessLevel.bitMask & self.userRole.bitMask) {
checkUser.resolve();
} else {
checkUser.reject('unauthorized');
}
},
function reject(httpObj) {
checkUser.reject(httpObj.status.toString());
}
);
/**
* I setted up the state change inside the promises success/error,
* so i can safely assign pendingStateChange back to null.
*/
self.pendingStateChange = null;
return checkUser.promise;
},
/**
* Public properties
*/
userRole: null,
user: {},
isLogged: null,
pendingStateChange: null,
doneLoading: null
};
getLoginData();
managePermissions();
return wrappedService;
};
});
from django.conf.urls import patterns, include, url
from django.views.generic import TemplateView
from rest_framework import routers
from api import QuestViewSet, TargetViewSet, IdeaViewSet, UserViewSet, GroupViewSet, WItemViewSet, UProductViewSet, ReviewPointViewSet
from apiauth import ObtainAuthToken, ObtainUser, ObtainLogout
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()
# Routers provide an easy way of automatically determining the URL conf
router = routers.DefaultRouter()
router.register(r'quests', QuestViewSet)
router.register(r'ideas', IdeaViewSet)
router.register(r'witems', WItemViewSet)
router.register(r'reviews', ReviewPointViewSet)
router.register(r'users', UserViewSet)
router.register(r'groups', GroupViewSet)
router.register(r'targets', TargetViewSet)
urlpatterns = patterns('',
url(r'^$', TemplateView.as_view(template_name='base.html')),
# API URLs
url(r'^api/', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
# Uncomment the admin/doc line below to enable admin documentation:
# url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
url(r'^admin/', include(admin.site.urls)),
url('', include('social.apps.django_app.urls', namespace='social')),
url(r"^account/", include("account.urls")),
url(r'^api-token/login/(?P<backend>[^/]+)/$', ObtainAuthToken.as_view()),
url(r'^api-token/user/', ObtainUser.as_view()),
url(r'^api-token/logout/', ObtainLogout.as_view())
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment