hobo/hobo/rest_authentication.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

195 lines
6.5 KiB
Python
Raw Normal View History

import logging
import requests
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import FieldDoesNotExist
from django.utils.module_loading import import_string
from rest_framework import authentication, exceptions, status
2021-05-14 18:39:27 +02:00
from hobo import signature
from hobo.requests_wrapper import Requests
try:
from mellon.models import UserSAMLIdentifier
except ImportError:
UserSAMLIdentifier = None
class AnonymousAuthenticServiceUser(AnonymousUser):
'''This virtual user hold permissions for other publik services'''
2021-05-14 18:39:27 +02:00
is_anonymous = True
is_authenticated = True
ou = None
is_publik_service = True
def has_perm(self, *args, **kwargs):
return True
def has_ou_perm(self, *args, **kwargs):
return True
def has_perm_any(self, *args, **kwargs):
return True
def filter_by_perm(self, perm_or_perms, qs):
# all objects are reachable
return qs
def __unicode__(self):
return 'Publik Service User'
class AnonymousAdminServiceUser(AnonymousUser):
'''This virtual user hold permissions for other publik services'''
2021-05-14 18:39:27 +02:00
is_staff = True
is_anonymous = True
is_authenticated = True
ou = None
is_publik_service = True
def __unicode__(self):
return 'Publik Service Admin'
class APIClientUser:
is_active = True
is_anonymous = False
is_authenticated = True
is_superuser = False
roles = []
def __init__(self, is_active, is_anonymous, is_authenticated, is_superuser, roles):
self.is_active = is_active
self.is_anonymous = is_anonymous
self.is_authenticated = is_authenticated
self.is_superuser = is_superuser
self.roles = roles
@classmethod
def from_dict(cls, data):
return cls(
is_active=data['is_active'],
is_anonymous=data['is_anonymous'],
is_authenticated=data['is_authenticated'],
is_superuser=data['is_superuser'],
roles=data['roles'],
)
class PublikAuthenticationFailed(exceptions.APIException):
status_code = status.HTTP_401_UNAUTHORIZED
default_code = 'invalid-signature'
def __init__(self, code):
self.detail = {'err': 1, 'err_desc': code}
class PublikAuthentication(authentication.BaseAuthentication):
def __init__(self, *args, **kwargs):
self.logger = logging.getLogger(__name__)
super().__init__(*args, **kwargs)
def resolve_user(self, request):
User = get_user_model()
if 'NameID' in request.GET:
name_id = request.GET['NameID']
is_authentic = True
try:
User._meta.get_field('uuid')
except FieldDoesNotExist:
is_authentic = False
if is_authentic:
try:
return User.objects.get(uuid=name_id)
except User.DoesNotExist:
raise PublikAuthenticationFailed('user-not-found')
elif UserSAMLIdentifier:
try:
return UserSAMLIdentifier.objects.get(name_id=name_id).user
except UserSAMLIdentifier.DoesNotExist:
raise PublikAuthenticationFailed('user-not-found')
else:
raise PublikAuthenticationFailed('no-usable-model')
else:
orig = request.GET['orig']
try:
return User.objects.get(username=orig)
except User.DoesNotExist:
pass
if hasattr(settings, 'HOBO_ANONYMOUS_SERVICE_USER_CLASS'):
klass = import_string(settings.HOBO_ANONYMOUS_SERVICE_USER_CLASS)
self.logger.info('anonymous signature validated')
return klass()
raise PublikAuthenticationFailed('no-user-for-orig')
def get_orig_key(self, orig):
if not hasattr(settings, 'KNOWN_SERVICES'):
self.logger.warning('no known services')
raise PublikAuthenticationFailed('no-known-services-setting')
for service_id in settings.KNOWN_SERVICES:
2018-03-27 16:13:33 +02:00
for slug, service in settings.KNOWN_SERVICES[service_id].items():
if service.get('verif_orig') == orig and service.get('secret'):
return service['secret']
self.logger.warning('no secret found for origin %r', orig)
raise PublikAuthenticationFailed('no-secret-found-for-orig')
def authenticate(self, request):
full_path = request.get_full_path()
if not request.GET.get('orig') or not request.GET.get('signature'):
return None
key = self.get_orig_key(request.GET['orig'])
try:
assert signature.check_url(
full_path, key, raise_on_error=True
), 'signature.check_url should never return False with raise_on_error'
except signature.SignatureError as e:
self.logger.warning('publik rest-framework-authentication failed: %s', e)
raise PublikAuthenticationFailed(str(e))
user = self.resolve_user(request)
self.logger.info('user authenticated with signature %s', user)
return (user, None)
class APIClientAuthenticationUnavailable(exceptions.APIException):
status_code = status.HTTP_503_SERVICE_UNAVAILABLE
default_code = 'IDP temporarily unavailable, try again later.'
def __init__(self):
self.detail = {'err': 1, 'err_desc': self.default_code}
class APIClientAuthentication(authentication.BasicAuthentication):
def authenticate_credentials(self, identifier, password, request=None):
idp_services = list(getattr(settings, 'KNOWN_SERVICES', {}).get('authentic', {}).values())
if not idp_services:
return None
authentic = idp_services[0]
url = authentic['url'] + 'api/check-api-client/'
try:
response = Requests().post(url, json={'identifier': identifier, 'password': password})
except requests.Timeout:
raise APIClientAuthenticationUnavailable()
except requests.RequestException as err:
raise APIClientAuthenticationUnavailable()
try:
response.raise_for_status()
except requests.exceptions.RequestException:
return None
result = response.json()
if 'err' not in result or 'data' not in result or result['err'] == 1:
return None
try:
api_client = APIClientUser.from_dict(result['data'])
except Exception:
return None
return api_client, None