# HG changeset patch # User durandn # Date 1457021180 -3600 # Node ID cfc868991b82a6759c113d42689fee4b511c7054 # Parent 50b532f5e6cbe51f75016a7f1ef4cb1226c6d92a Added custom user model to store extra data from the GED + corrected signals and api auth class so they interface correctly with the ged diff -r 50b532f5e6cb -r cfc868991b82 server/src/metaeducation/admin.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/server/src/metaeducation/admin.py Thu Mar 03 17:06:20 2016 +0100 @@ -0,0 +1,76 @@ +from django import forms +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin as BaseUserAdmin +from django.contrib.auth.forms import ReadOnlyPasswordHashField + +from .models import User + + +class UserCreationForm(forms.ModelForm): + """A form for creating new users. Includes all the required + fields, plus a repeated password.""" + password1 = forms.CharField(label='Password', widget=forms.PasswordInput) + password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput) + + class Meta: + model = User + fields = ('external_id', 'username', 'first_name', 'last_name') + + def clean_password2(self): + # Check that the two password entries match + password1 = self.cleaned_data.get("password1") + password2 = self.cleaned_data.get("password2") + if password1 and password2 and password1 != password2: + raise forms.ValidationError("Passwords don't match") + return password2 + + def save(self, commit=True): + # Save the provided password in hashed format + user = super(UserCreationForm, self).save(commit=False) + user.set_password(self.cleaned_data["password1"]) + if commit: + user.save() + return user + + +class UserChangeForm(forms.ModelForm): + """A form for updating users. Includes all the fields on + the user, but replaces the password field with admin's + password hash display field. + """ + password = ReadOnlyPasswordHashField() + + class Meta: + model = User + fields = ('external_id', 'username', 'first_name', 'last_name', 'password', 'is_active', 'is_staff') + + +class UserAdmin(BaseUserAdmin): + # The forms to add and change user instances + form = UserChangeForm + add_form = UserCreationForm + + # The fields to be used in displaying the User model. + # These override the definitions on the base UserAdmin + # that reference specific fields on auth.User. + list_display = ('external_id', 'username', 'first_name', 'last_name', 'password', 'is_staff') + list_filter = ('is_staff',) + fieldsets = ( + (None, {'fields': ('external_id', 'username')}), + ('Personal info', {'fields': ('username', 'first_name', 'last_name')}), + ('Permissions', {'fields': ('is_staff',)}), + ) + # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin + # overrides get_fieldsets to use this attribute when creating a user. + add_fieldsets = ( + (None, { + 'classes': ('wide',), + 'fields': ('external_id', 'username', 'first_name', 'last_name', 'password1', 'password2')} + ), + ) + search_fields = ('username',) + ordering = ('username',) + filter_horizontal = () + +# Now register the new UserAdmin +admin.site.register(User, UserAdmin) \ No newline at end of file diff -r 50b532f5e6cb -r cfc868991b82 server/src/metaeducation/auth.py --- a/server/src/metaeducation/auth.py Thu Mar 03 16:28:46 2016 +0100 +++ b/server/src/metaeducation/auth.py Thu Mar 03 17:06:20 2016 +0100 @@ -2,6 +2,7 @@ from django.contrib.auth import get_user_model, login from django.contrib.auth.models import Permission from django.conf import settings +from urllib import parse import requests import re import json @@ -10,22 +11,22 @@ def authenticate(self, request): # get token, get username - if 'act_as' not in request.GET or 'HTTP_RENKAN_ACT_AS' not in request.META: + if ("act_as" not in request.GET) and ('HTTP_RENKAN_ACT_AS' not in request.META): return else: - username = request.GET.get('act_as', request.META.get("HTTP_RENKAN_ACT_AS", "")) + external_id = request.GET.get('act_as', request.META.get("HTTP_RENKAN_ACT_AS", "")) try: - user = get_user_model().objects.get(username=username) + user = get_user_model().objects.get(external_id=external_id) except get_user_model().DoesNotExist: return if 'HTTP_AUTHORIZATION' not in request.META: return else: token = re.search("(?<=\s).*", request.META["HTTP_AUTHORIZATION"]).group(0) - + print(token) # send token to Oauth server token_validate_response = requests.get( - settings.MTDC_VALIDATE_TOKEN_URL+token + settings.MTDC_VALIDATE_TOKEN_BASE_URL+token+"?redirect_uri="+parse.quote_plus(settings.MTDC_GED_BASE_URL) ) if token_validate_response.status_code != 200: return diff -r 50b532f5e6cb -r cfc868991b82 server/src/metaeducation/migrations/0001_initial.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/server/src/metaeducation/migrations/0001_initial.py Thu Mar 03 17:06:20 2016 +0100 @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.1 on 2016-03-03 10:40 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models +import django.utils.timezone +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0007_alter_validators_add_error_messages'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('external_id', models.CharField(default=uuid.uuid4, max_length=256, unique=True)), + ('username', models.CharField(max_length=150, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')])), + ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=30, verbose_name='last name')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now)), + ('is_active', models.BooleanField(default=True)), + ('is_staff', models.BooleanField(default=False)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), + ], + options={ + 'abstract': False, + }, + ), + ] diff -r 50b532f5e6cb -r cfc868991b82 server/src/metaeducation/migrations/__init__.py diff -r 50b532f5e6cb -r cfc868991b82 server/src/metaeducation/models.py --- a/server/src/metaeducation/models.py Thu Mar 03 16:28:46 2016 +0100 +++ b/server/src/metaeducation/models.py Thu Mar 03 17:06:20 2016 +0100 @@ -1,8 +1,64 @@ -from django.contrib.auth.models import AbstractUser -from django.db.models import CharField +from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin +from django.db import models +from django.core import validators +from django.utils.translation import ugettext_lazy as _ +from django.utils import timezone + +import uuid + +class UserManager(BaseUserManager): + + def create_user(self, external_id, username, password=None, **extra_fields): + """ + Creates and saves a User with the given username, email and password. + """ + now = timezone.now() + user = self.model(external_id=external_id, username=username, + is_staff=False, is_active=True, is_superuser=False, + date_joined=now, **extra_fields) + user.set_password(password) + user.save(using=self._db) + return user -class User(AbstractUser): - external_id = CharField(max_length=256, blank=True, null=True) + def create_superuser(self, external_id, username, password=None, **extra_fields): + superuser = self.create_user(external_id, username, password, **extra_fields) + superuser.is_staff = True + superuser.is_active = True + superuser.is_superuser = True + superuser.save(using=self._db) + return superuser + +class User(AbstractBaseUser, PermissionsMixin): + external_id = models.CharField(max_length=256, unique=True, default=uuid.uuid4) + username = models.CharField(max_length=150, + validators=[ + validators.RegexValidator( + r'^[\w.@+-]+$', + _('Enter a valid username. This value may contain only ' + 'letters, numbers ' 'and @/./+/-/_ characters.') + ), + ], + ) + first_name = models.CharField(_('first name'), max_length=30, blank=True) + last_name = models.CharField(_('last name'), max_length=30, blank=True) + date_joined = models.DateTimeField(default=timezone.now) + is_active = models.BooleanField(default=True) + is_staff = models.BooleanField(default=False) + + objects = UserManager() + + USERNAME_FIELD = 'external_id' + REQUIRED_FIELDS = ['username'] + + def get_full_name(self): + """ + Returns the first_name plus the last_name, with a space in between. + """ + full_name = '%s %s' % (self.first_name, self.last_name) + return full_name.strip() + + def get_short_name(self): + return self.first_name def __unicode__(self): return self.external_id \ No newline at end of file diff -r 50b532f5e6cb -r cfc868991b82 server/src/metaeducation/mtdc_oauth_provider/views.py --- a/server/src/metaeducation/mtdc_oauth_provider/views.py Thu Mar 03 16:28:46 2016 +0100 +++ b/server/src/metaeducation/mtdc_oauth_provider/views.py Thu Mar 03 17:06:20 2016 +0100 @@ -86,7 +86,6 @@ def complete_login(self, request, app, token, **kwargs): resp = requests.get(self.profile_url, params={'access_token': token.token}) - print(resp.text) extra_data = resp.json() if request.session.get("OAUTH_CONTEXT_BASE_URL", None) is not None: del request.session["OAUTH_CONTEXT_BASE_URL"] diff -r 50b532f5e6cb -r cfc868991b82 server/src/metaeducation/settings/__init__.py --- a/server/src/metaeducation/settings/__init__.py Thu Mar 03 16:28:46 2016 +0100 +++ b/server/src/metaeducation/settings/__init__.py Thu Mar 03 17:06:20 2016 +0100 @@ -119,13 +119,11 @@ ANONYMOUS_USER_ID = -1 DEFAULT_RENKAN_ICON = "" +RENKAN_USER_DISPLAY_FIELD = 'external_id' -#ACCOUNT_AUTHENTICATION_METHOD = "username" -#ACCOUNT_ADAPTER = "allauth.account.adapter.DefaultAccountAdapter" SOCIALACCOUNT_ADAPTER = "metaeducation.mtdc_oauth_provider.views.MtdcOAuth2Adapter" SOCIALACCOUNT_PROVIDERS = { 'mtdc': { 'SCOPE': ['basic'] } } - diff -r 50b532f5e6cb -r cfc868991b82 server/src/metaeducation/settings/dev.py.tmpl --- a/server/src/metaeducation/settings/dev.py.tmpl Thu Mar 03 16:28:46 2016 +0100 +++ b/server/src/metaeducation/settings/dev.py.tmpl Thu Mar 03 17:06:20 2016 +0100 @@ -38,14 +38,17 @@ ANONYMOUS_USER_ID = -1 DEFAULT_RENKAN_ICON = "" +RENKAN_TOOL_ID = "" # Renkan ID, provided by ITOP MTDC_ACCESS_TOKEN_URL = "" # This URL is the access token endpoint URL, relative to a Base url that will be passed as query arg to the server MTDC_AUTHORIZE_URL = "" # This URL is the authorize endpoint URL, relative to a Base url that will be passed as query arg to the server MTDC_PROFILE_URL = "" # This URL is the user profile endpoint URL, relative to a Base url that will be passed as query arg to the server +MTDC_GED_BASE_URL = "" # Base URL of the GED server MTDC_VALIDATE_TOKEN_URL = "" # This URL is the ABSOLUTE url for validating a token. There will be no context involved for validation token from server to server. -MTDC_REFERENCE_RESOURCE_BASE_URL = "" # This URL is the ABSOLUTE url for referencing a resource (ged webservice) MTDC_CLIENT_CREDENTIALS_TOKEN_URL = "" # This URL is the ABSOLUTE url for getting a token via Client Credentials from server to server. +MTDC_REFERENCE_RESOURCE_BASE_URL = "" # This URL is the ABSOLUTE url for the GED resource referencement service from server to server. +OAUTH_REDIRECT_URI = "" # Redirect URI for the GED server, used when validating GED tokens during client credentials flow. -MTDC_USERNAME_FIELD_NAME = "username" # Name of the username field that will be transmitted in response to the profile url request (usually "username", for metaeduc will probably be "displayName") +MTDC_USERNAME_FIELD_NAME = "displayName" # Name of the username field that will be transmitted in response to the profile url request (usually "username", for metaeduc will probably be "displayName") MTDC_USERID_FIELD_NAME = "id" # Name of the user id field that will be transmitted in response to the profile url request (usually "id") \ No newline at end of file diff -r 50b532f5e6cb -r cfc868991b82 server/src/metaeducation/signals.py --- a/server/src/metaeducation/signals.py Thu Mar 03 16:28:46 2016 +0100 +++ b/server/src/metaeducation/signals.py Thu Mar 03 17:06:20 2016 +0100 @@ -2,9 +2,9 @@ from django.dispatch import receiver from django.contrib.auth import get_user_model from django.conf import settings -import requests, json +from django.core.urlresolvers import reverse +import requests, json, sys -@receiver(post_save) def reference_created_renkan(sender, instance, created, **kwargs): from renkanmanager.models import Renkan if created and sender == Renkan and not instance.source_revision_guid: @@ -18,14 +18,25 @@ if token_response.status_code == 200: token = json.loads(token_response.text)['access_token'] post_data = json.dumps({ - "userId": instance.creator.external_id, + "id": str(instance.renkan_guid), + "idOutil": settings.RENKAN_TOOL_ID, + "idUser": instance.creator.external_id, + "pf": "ITOPPP", + "uai": "0310000W", + "redirect_uri": settings.OAUTH_REDIRECT_URI, "title": instance.title }) - print(post_data) + resource_ws_url = settings.MTDC_REFERENCE_RESOURCE_BASE_URL# + str(instance.renkan_guid) + reference_response = requests.post( - settings.MTDC_REFERENCE_RESOURCE_BASE_URL+str(instance.renkan_guid), + resource_ws_url, data = post_data, headers = { - "Authorization": "Bearer %s" % token + "Authorization": "Bearer %s" % token, + "content-type": "application/json" } - ) \ No newline at end of file + ) + + +if not 'test' in sys.argv: + post_save.connect(reference_created_renkan) \ No newline at end of file