201 lines
6.8 KiB
Python
201 lines
6.8 KiB
Python
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
|
|
|
|
from hobo import signature
|
|
from hobo.provisionning.utils import ProvisionningTemporaryError, get_or_create_user_from_name_id
|
|
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'''
|
|
|
|
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'''
|
|
|
|
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
|
|
|
|
def __init__(self, code, description=None):
|
|
self.detail = {'err': 1, 'err_class': code, 'err_desc': code}
|
|
if description:
|
|
self.detail['err_desc'] = description
|
|
|
|
|
|
class PublikAuthenticationTemporaryFailure(PublikAuthenticationFailed):
|
|
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
|
|
|
|
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 get_or_create_user_from_name_id(name_id, raise_on_missing=True)
|
|
except ProvisionningTemporaryError as e:
|
|
raise PublikAuthenticationTemporaryFailure('idp-not-reachable', str(e))
|
|
except User.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)
|
|
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 services in settings.KNOWN_SERVICES.values():
|
|
for service in services.values():
|
|
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('invalid-signature', str(e))
|
|
user = self.resolve_user(request)
|
|
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, userid, 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': userid, 'password': password})
|
|
except requests.Timeout:
|
|
raise APIClientAuthenticationUnavailable()
|
|
except requests.RequestException:
|
|
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
|