Skip to content
auth.py 41.5 KiB
Newer Older
    return render(request, "user-profile-form.html", {
        "is_inst_id_locked": is_inst_id_locked(request.user),
        "enable_inst_id_if_not_locked": (
            request.GET.get("first_login")
            or (request.GET.get("set_inst_id")
                and request.GET["referer"])
            ),
        "user_form": user_form,
        })
# {{{ SAML auth backend

# This ticks the 'verified' boxes once we've receive attribute assertions
# through SAML2.

class Saml2Backend(Saml2BackendBase):
    def _set_attribute(self, obj, attr, value):
        mod = super(Saml2Backend, self)._set_attribute(obj, attr, value)

        if attr == "institutional_id":
            if not obj.institutional_id_verified:
                obj.institutional_id_verified = True
                mod = True

        if attr in ["first_name", "last_name"]:
            if not obj.name_verified:
                obj.name_verified = True
                mod = True

        if attr == "email":
            from course.constants import user_status
            if obj.status != user_status.active:
                obj.status = user_status.active
                mod = True

# {{{ sign-out

def sign_out_confirmation(request, redirect_field_name=REDIRECT_FIELD_NAME):
    redirect_to = request.POST.get(redirect_field_name,
                                   request.GET.get(redirect_field_name, ''))

    next_uri = ""
    if redirect_to:
        next_uri = "?%s=%s" % (redirect_field_name, redirect_to)

    return render(request, "sign-out-confirmation.html",
                  {"next_uri": next_uri})


@never_cache
def sign_out(request, redirect_field_name=REDIRECT_FIELD_NAME):
    redirect_to = request.POST.get(redirect_field_name,
                                   request.GET.get(redirect_field_name, ''))
Andreas Klöckner's avatar
Andreas Klöckner committed
    response = None
    if settings.RELATE_SIGN_IN_BY_SAML2_ENABLED:
        from djangosaml2.views import _get_subject_id, logout as saml2_logout
        if _get_subject_id(request.session) is not None:
Andreas Klöckner's avatar
Andreas Klöckner committed
            response = saml2_logout(request)

    auth_logout(request)
Andreas Klöckner's avatar
Andreas Klöckner committed
    if response is not None:
        return response
Andreas Klöckner's avatar
Andreas Klöckner committed
    else:
        return redirect("relate-home")

# {{{ API auth

def find_matching_token(token_id=None, token_hash_str=None, now_datetime=None):
    try:
        token = AuthenticationToken().objects.get(id=token_id)
    except AuthenticationToken.DoesNotExist:
        return None

    from django.contrib.auth.hashers import check_password
    if not check_password(token, token_hash_str):
        return None

    if token.revocation_time is not None:
        return None
    if now_datetime > token.valid_until:
        return None

    return token


class APIBearerTokenBackend(object):
    def authenticate(self, token_id=None, token_hash_str=None, now_datetime=None):
        token = find_matching_token(token_id, token_hash_str, now_datetime)

        token.last_use_time = now_datetime
        token.save()

        return token.user

    def get_user(self, user_id):
        try:
            return get_user_model().objects.get(pk=user_id)
        except get_user_model().DoesNotExist:
            return None


AUTH_HEADER_RE = re.compile("^Token ([0-9]+)_([a-z0-9]+)$")


def with_course_api_auth(f):
    def wrapper(request, course_identifier, *args, **kwargs):
        from django.utils.timezone import now
        now_datetime = now()

        auth_header = request.META.get("AUTHORIZATION", None)

        match = AUTH_HEADER_RE.match(auth_header)
        if match is None:
            raise PermissionDenied("ill-formed Authorization header")

        auth_data = dict(
                token_id=int(AUTH_HEADER_RE.group(1)),
                token_hash_str=AUTH_HEADER_RE.group(2),
                now_datetime=now_datetime)

        # FIXME: Redundant db roundtrip
        token = find_matching_token(**auth_data)

        from django.contrib.auth import authenticate, login
        user = authenticate(**auth_data)

        assert user is not None

        login(request, user)

        response = f(
                token.participation,
                token.restrict_to_participation_role,
                *args, **kwargs)

        return response

    from functools import update_wrapper
    update_wrapper(wrapper, f)

    return wrapper

# }}}


# {{{ manage API auth tokens

class AuthenticationTokenForm(StyledModelForm):
    class Meta:
        model = AuthenticationToken
        fields = (
                "restrict_to_participation_role",
                "description",
                "valid_until",
                )

    def __init__(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        super(AuthenticationTokenForm, self).__init__(*args, **kwargs)

        self.helper.add_input(Submit("create", _("Create")))


def manage_authentication_tokens(request):
    # type: (http.HttpRequest) -> http.HttpResponse
    if not request.user.is_authenticated:
        raise PermissionDenied()

    if request.method == 'POST':
        form = AuthenticationTokenForm(request.POST)
        if form.is_valid():
            token = make_sign_in_key(request.user)
            from django.contrib.auth.hashers import make_password
            request.user.git_auth_token_hash = make_password(token)
            request.user.save()

            messages.add_message(request, messages.SUCCESS,
                    _("A new authentication token has been set: %s.")
                    % token)

    else:
        form = AuthenticationTokenForm()

    tokens = AuthenticationToken.objects.filter(
            user=request.user,
            revocation_time=None)

    return render(request, "course/manage-auth-tokens.html", {
        "form": form,
        "new_token_message": "",
        "tokens": tokens,
        })

# }}}

# vim: foldmethod=marker