diff --git a/README b/README index 83c50be..c528c94 100644 --- a/README +++ b/README @@ -33,9 +33,7 @@ For simple dev you need at least: - settings.FILESERVER_USER might need some sudo access # TODO - - Rewrite usersync.neuf.no as one or more endpoints [DONE?] - On password update, then update ldap hash on InsideUser.ldap_password - - allow non LDAP-users (conditional LDAP-stuff), because we don't want LDAP filled with non-active members - Integrity check view which provides two way diffs for the following: - user vs user group - user vs kerberos principal diff --git a/inside/authentication.py b/inside/authentication.py index 8a0a614..2126cea 100644 --- a/inside/authentication.py +++ b/inside/authentication.py @@ -11,69 +11,79 @@ logger = logging.getLogger(__name__) class InsideBackend(object): """ - Authenticate against the inside database + Authenticate against the inside database by username, passord or email, password. """ + user = None + inside_user = None def authenticate(self, username=None, password=None): password_hash = utils.mysql_create(password) - inside_user = InsideUser.objects.filter( + self.inside_user = InsideUser.objects.filter( Q(password=password_hash) & (Q(ldap_username=username) | Q(email=username)) ) # TODO log all login attempts # AuthLog.objects.create() - if len(inside_user) != 1: + if len(self.inside_user) != 1: return None - - inside_user = inside_user[0] + self.inside_user = self.inside_user[0] try: - user = User.objects.get(username=inside_user.ldap_username) + self.user = User.objects.get(username=self.inside_user.ldap_username) except User.DoesNotExist: # Create a new user. - user = User(username=inside_user.ldap_username, password=password) + self.user = User(username=self.inside_user.ldap_username, password=password) # Sync detail and groups from Inside to local Django install (for display and permissions) - user = self._update_user_details(user, inside_user) - user.save() + self._update_user_details() + self.user.save() - user = self._update_groups(user, inside_user) + self._update_groups() - return user + self._update_user_flags() + self.user.save() - def get_user(self, user_id): - try: - return User.objects.get(pk=user_id) - except User.DoesNotExist: - return None + return self.user - def _update_user_details(self, user, inside_user): + def _update_user_details(self): sync_attrs = { 'firstname': 'first_name', 'lastname': 'last_name', 'email': 'email' } for inside_attr, local_attr in sync_attrs.iteritems(): - setattr(user, local_attr, getattr(inside_user, inside_attr)) + # Copy from Inside user to local Django user + setattr(self.user, local_attr, getattr(self.inside_user, inside_attr)) - return user - - def _update_groups(self, user, inside_user): - existing_groups = list(user.groups.all()) - inside_groups = InsideGroup.objects.filter(user_rels__user=inside_user).exclude(posix_group='') + def _update_groups(self): + existing_groups = list(self.user.groups.all()) + inside_groups = InsideGroup.objects.filter(user_rels__user=self.inside_user).exclude(posix_group='') inside_groups = inside_groups.values_list('posix_group', flat=True) # Add new relationships for group in inside_groups: g, created = Group.objects.get_or_create(name=group) - user.groups.add(g) + self.user.groups.add(g) if not settings.INSIDE_GROUPS_SYNC_DELETE: - return user + return # Remove old relationships for g in existing_groups: if g.name not in inside_groups: - user.groups.remove(g) + self.user.groups.remove(g) - return user + def _update_user_flags(self): + flag_to_group = settings.INSIDE_USER_FLAGS_SYNC + user_groups = self.user.groups.values_list('name', flat=True) + for flag, group in flag_to_group.iteritems(): + if group in user_groups: + setattr(self.user, flag, True) + else: + setattr(self.user, flag, False) + + def get_user(self, user_id): + try: + return User.objects.get(pk=user_id) + except User.DoesNotExist: + return None \ No newline at end of file diff --git a/neuf_kerberos/views.py b/neuf_kerberos/views.py index 9e612e5..2b60ef2 100644 --- a/neuf_kerberos/views.py +++ b/neuf_kerberos/views.py @@ -1,14 +1,14 @@ -from django.contrib.auth.decorators import login_required -from django.http import JsonResponse +from rest_framework.decorators import api_view +from rest_framework.response import Response from neuf_kerberos.utils import get_kerberos_principal, format_krb5_date -@login_required +@api_view(['GET', 'OPTIONS', 'HEAD']) def client_status(request): - krb5_principal = get_kerberos_principal(request.GET.get('username')) + krb5_principal = get_kerberos_principal(request.QUERY_PARAMS.get('username')) if not krb5_principal: - return JsonResponse({'active': False}) + return Response({'active': False}) last_succ_auth = format_krb5_date(krb5_principal['Last successful authentication']) status = { @@ -17,4 +17,4 @@ def client_status(request): 'last_modified': krb5_principal['Last modified'] } - return JsonResponse(status) \ No newline at end of file + return Response(status) \ No newline at end of file diff --git a/neuf_ldap/models.py b/neuf_ldap/models.py index 64b1635..694d3a1 100644 --- a/neuf_ldap/models.py +++ b/neuf_ldap/models.py @@ -52,7 +52,8 @@ class LdapUser(ldapdb.models.Model): # Hack to use django internals for password reset last_login = datetime.datetime(2001, 1, 1) - def _days_since_1970(self): + @staticmethod + def _days_since_1970(): epoch = datetime.datetime.utcfromtimestamp(0) today = datetime.datetime.today() delta = today - epoch @@ -62,7 +63,7 @@ class LdapUser(ldapdb.models.Model): def set_password(self, raw_password, commit=True): self.password = ldap_create(raw_password) # Update last changed password date - self.shadowLastChange = self._days_since_1970() + self.shadowLastChange = LdapUser._days_since_1970() if commit: self.save() diff --git a/neuf_ldap/serializers.py b/neuf_ldap/serializers.py new file mode 100644 index 0000000..8e11c13 --- /dev/null +++ b/neuf_ldap/serializers.py @@ -0,0 +1,22 @@ +from neuf_ldap.models import LdapUser, LdapGroup, LdapAutomountHome +from rest_framework import serializers + + +class UserSerializer(serializers.HyperlinkedModelSerializer): + user_group = serializers.HyperlinkedIdentityField(view_name='ldapgroup-detail') + + class Meta: + model = LdapUser + fields = ('url', 'username', 'first_name', 'last_name', 'email', 'user_group') # 'dn', 'id', 'group',) + + +class StringListField(serializers.ListField): + child = serializers.CharField() + + +class GroupSerializer(serializers.HyperlinkedModelSerializer): + # members = StringListField() + + class Meta: + model = LdapGroup + fields = ('url', 'name',) # 'dn', 'gid', 'members',) diff --git a/neuf_ldap/urls.py b/neuf_ldap/urls.py new file mode 100644 index 0000000..75f35b6 --- /dev/null +++ b/neuf_ldap/urls.py @@ -0,0 +1,12 @@ +from django.conf.urls import url, include +from rest_framework import routers + +from neuf_ldap import views + +router = routers.DefaultRouter() +router.register(r'users', views.UserViewSet) +router.register(r'groups', views.GroupViewSet) + +urlpatterns = [ + url(r'^ldap/', include(router.urls)), +] \ No newline at end of file diff --git a/neuf_ldap/views.py b/neuf_ldap/views.py index 91ea44a..34e7c6c 100644 --- a/neuf_ldap/views.py +++ b/neuf_ldap/views.py @@ -1,3 +1,37 @@ -from django.shortcuts import render +from neuf_ldap.models import LdapGroup, LdapUser +from neuf_ldap.serializers import UserSerializer, GroupSerializer +from rest_framework import viewsets, filters -# Create your views here. + +class UserViewSet(viewsets.ReadOnlyModelViewSet): + """ + API endpoint that allows users to be viewed + """ + queryset = LdapUser.objects.all() + serializer_class = UserSerializer + + def get_queryset(self): + if self.request.user.is_superuser: + return super(UserViewSet, self).get_queryset() + + return LdapUser.objects.filter(username=self.request.user.username) + + +class GroupViewSet(viewsets.ReadOnlyModelViewSet): + """ + API endpoint that allows groups to be viewed + """ + queryset = LdapGroup.objects.all() + serializer_class = GroupSerializer + filter_backends = (filters.DjangoFilterBackend,) + filter_fields = ('members',) + + def get_queryset(self): + if self.request.user.is_superuser: + member = self.request.QUERY_PARAMS.get('member') + if member: + return LdapGroup.objects.filter(members__contains=member) + + return super(GroupViewSet, self).get_queryset() + + return LdapGroup.objects.filter(members__contains=self.request.user.username) \ No newline at end of file diff --git a/neuf_radius/views.py b/neuf_radius/views.py index 0507bbc..e5a810f 100644 --- a/neuf_radius/views.py +++ b/neuf_radius/views.py @@ -1,15 +1,15 @@ -from django.contrib.auth.decorators import login_required -from django.http import JsonResponse from neuf_radius.models import Radcheck, Radpostauth +from rest_framework.decorators import api_view +from rest_framework.response import Response -@login_required +@api_view(['GET', 'OPTIONS', 'HEAD']) def wireless_status(request): - username = request.GET.get('username') + username = request.QUERY_PARAMS.get('username') try: radius_user = Radcheck.objects.get(username=username) except Radcheck.DoesNotExist: - return JsonResponse({'active': False}) + return Response({'active': False}) # get last authentication last_auth = Radpostauth.objects.filter(username__iexact=username, reply='Access-Accept').order_by('authdate') @@ -17,4 +17,4 @@ def wireless_status(request): if len(last_auth) != 0: status['last_successful_auth'] = last_auth[0].authdate.strftime('%Y-%m-%d %H:%M:%S') - return JsonResponse(status) \ No newline at end of file + return Response(status) \ No newline at end of file diff --git a/neuf_userinfo/urls.py b/neuf_userinfo/urls.py index 5f64d7b..2028183 100644 --- a/neuf_userinfo/urls.py +++ b/neuf_userinfo/urls.py @@ -1,11 +1,13 @@ from django.conf.urls import patterns, url + from neuf_userinfo.forms import NeufSetPasswordForm from neuf_userinfo.views import AddNewUserView urlpatterns = patterns( 'neuf_userinfo.views', url(r'^$', 'index', name='index'), - url(r'^accounts/profile/(?P\w*)$', 'profile'), + url(r'^accounts/profile/(?P\w*)$', 'profile', name='profile'), + url(r'^accounts/profile/$', 'profile', name='my-profile'), url(r'^accounts/logout/$', 'logout'), # Set password url(r'^accounts/password/reset/confirm/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', diff --git a/neuf_userinfo/views.py b/neuf_userinfo/views.py index e896324..fedc105 100644 --- a/neuf_userinfo/views.py +++ b/neuf_userinfo/views.py @@ -18,7 +18,6 @@ import logging from inside.models import InsideUser from neuf_kerberos.utils import kerberos_create_principal -from neuf_ldap.models import LdapGroup, LdapUser from neuf_ldap.utils import ldap_create_user, ldap_create_automount from neuf_radius.utils import radius_create_user from neuf_userinfo.forms import NewUserForm @@ -35,7 +34,7 @@ def index(request): login(request, form.get_user()) else: if request.user.is_authenticated(): - return HttpResponseRedirect(reverse('neuf_userinfo.views.profile')) + return HttpResponseRedirect(reverse('my-profile')) else: form = AuthenticationForm(request) @@ -43,34 +42,18 @@ def index(request): @login_required -def profile(request, username=None): - if not username: - user = request.user - username = request.user.username - else: +def profile(request, username=''): + if username != '' and not request.user.is_superuser: # Only allow superusers - if not request.user.is_superuser: - return HttpResponseNotFound() + return HttpResponseNotFound() + if username != '': user = get_object_or_404(User, username=username) - - # TODO: Move LDAP stuff to JSON-view and load via AJAX (more fault tolerant) - ldap_user = None - ldap_groups = None - ldap_private_group = None - try: - ldap_user = LdapUser.objects.get(username=username) - except LdapUser.DoesNotExist: - pass - - if ldap_user: - ldap_groups = LdapGroup.objects.filter(usernames__contains=username) - ldap_private_group = LdapGroup.objects.get(gid=ldap_user.group) + else: + user = request.user response = { - 'ldap_user': ldap_user, - 'ldap_groups': ldap_groups, - 'ldap_private_group': ldap_private_group, + 'groups': user.groups.all(), 'user': user } return render(request, 'private/profile.html', response) diff --git a/requirements.txt b/requirements.txt index 4334d8b..daa8a48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ Django==1.7.4 django-auth-ldap==1.2.0 django-extensions==1.5.0 django-ldapdb==0.3.2 +djangorestframework==3.0.5 dj-static==0.0.6 Fabric==1.10.1 mysqlclient==1.3.5 diff --git a/templates/private/profile.html b/templates/private/profile.html index e69e6e0..3c5b258 100644 --- a/templates/private/profile.html +++ b/templates/private/profile.html @@ -18,6 +18,7 @@

Dine tjenester

    +
  • Felles innlogging (DNS-konto)
  • Trådløst nettverk
  • Foreningsmaskiner
@@ -27,9 +28,8 @@

Dine grupper

    - {% if ldap_groups %} - {% if ldap_private_group %}
  • {{ ldap_private_group }}
  • {% endif %} - {% for group in ldap_groups %} + {% if groups %} + {% for group in groups %} {% if group = "dns-aktiv" %}
  • {{ group }}
  • {% else %} @@ -50,7 +50,7 @@ diff --git a/userinfosite/settings.py b/userinfosite/settings.py index 9c21f39..10234de 100644 --- a/userinfosite/settings.py +++ b/userinfosite/settings.py @@ -38,6 +38,7 @@ INSTALLED_APPS = ( 'django_extensions', ) INSTALLED_APPS += ( + 'rest_framework', 'neuf_userinfo', 'neuf_ldap', 'neuf_radius', @@ -196,18 +197,17 @@ AUTH_LDAP_E_USER_FLAGS_BY_GROUP = AUTH_LDAP_U_USER_FLAGS_BY_GROUP AUTH_LDAP_E_USER_ATTR_MAP = AUTH_LDAP_U_USER_ATTR_MAP AUTH_LDAP_E_ALWAYS_UPDATE_USER = AUTH_LDAP_U_ALWAYS_UPDATE_USER -# Debug logging -import logging -logger = logging.getLogger('django_auth_ldap') -logger.addHandler(logging.StreamHandler()) -logger.setLevel(logging.DEBUG) - # Kerberos realm KERBEROS_REALM = "NEUF.NO" KERBEROS_PASSWORD_CHANGING_PRINCIPAL = 'brukerinfo' KERBEROS_PASSWORD = '' # Inside +INSIDE_USER_FLAGS_SYNC = { + "is_active": "dns-alle", + # "is_staff": "edb", + # "is_superuser": "edbadmin" +} INSIDE_GROUPS_SYNC_DELETE = True # When user logs in, groups are synced (locally in Django), this deletes aswell INSIDE_USERSYNC_API_KEY = '' INSIDE_USERSYNC_ENC_KEY = '' @@ -217,6 +217,11 @@ FILESERVER_HOST = "localhost" FILESERVER_USER = 'nikolark' # change this to your own user for development FILESERVER_HOME_PATH = "/tmp/homes" +REST_FRAMEWORK = { + 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAuthenticated',), + 'PAGINATE_BY': 10 +} + try: from local_settings import * except ImportError: diff --git a/userinfosite/urls.py b/userinfosite/urls.py index 0386420..cc194a0 100644 --- a/userinfosite/urls.py +++ b/userinfosite/urls.py @@ -9,9 +9,11 @@ admin.autodiscover() urlpatterns = patterns( '', url(r'', include('neuf_userinfo.urls')), + url(r'', include('neuf_ldap.urls')), url(r'^userstatus/client/$', 'neuf_kerberos.views.client_status'), url(r'^userstatus/wireless/$', 'neuf_radius.views.wireless_status'), + url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), url(r'^admin/', include(admin.site.urls)), )