Created
February 11, 2014 21:21
Revisions
-
jproby created this gist
Feb 11, 2014 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,99 @@ # 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': ''}) 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,87 @@ 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'); }); 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,345 @@ # -*- 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 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,207 @@ 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; }; }); 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,52 @@ 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()) )