def check_external_app(app, request=None): ''' Checks possible connection with any application server ''' try: url = get_external_app_url(app, request) + '/' + settings.TOKEN_URL headers = get_external_app_auth_header(app) except KeyError: logger.warning(MSG_EXTERNAL_APP_ERR.format(app=app)) return False try: # check that the server is up h = exec_request(method='head', url=url) assert h.status_code == 403 # expected response 403 Forbidden logger.info(MSG_EXTERNAL_APP_UP.format(app=app, url=url)) try: # check that the token is valid g = exec_request(method='get', url=url, headers=headers) g.raise_for_status() # expected response 200 OK logger.info(MSG_EXTERNAL_APP_TOKEN_OK.format(app=app, url=url)) return True # it's possible to connect with server :D except Exception: logger.warning(MSG_EXTERNAL_APP_TOKEN_ERR.format(app=app, url=url)) except Exception: logger.warning(MSG_EXTERNAL_APP_SERVER_ERR.format(app=app, url=url)) return False # it's not possible to connect with server :(
def check_realm(realm): ''' Checks if the realm name is valid visiting its keycloak server login page. ''' response = exec_request(method='head', url=f'{_KC_URL}/{realm}/account') response.raise_for_status()
def _get_user_info(realm, token): response = exec_request( method='get', url=f'{_KC_URL}/{realm}/{_KC_OID_URL}/userinfo', headers={'Authorization': f'Bearer {token}'}, ) response.raise_for_status() return response.json()
def _user_logged_out(sender, user, request, **kwargs): ''' Removes realm and token from session also logs out from keycloak server making the user token invalid. ''' token = request.session.get(_KC_TOKEN_SESSION) realm = get_current_realm(request, default_realm=None) if token and realm: # logout exec_request( method='post', url=f'{_KC_URL}/{realm}/{_KC_OID_URL}/logout', data={ 'client_id': settings.KEYCLOAK_CLIENT_ID, 'refresh_token': token['refresh_token'], }, )
def refresh_kc_token(realm, token): return exec_request( method='post', url=f'{_KC_URL}/{realm}/{_KC_OID_URL}/token', data={ 'grant_type': 'refresh_token', 'client_id': settings.KEYCLOAK_CLIENT_ID, 'refresh_token': token['refresh_token'], }, )
def _authenticate(realm, data): # get user token from the returned "code" response = exec_request( method='post', url=f'{_KC_URL}/{realm}/{_KC_OID_URL}/token', data=data, ) response.raise_for_status() token = response.json() userinfo = _get_user_info(realm, token['access_token']) return token, userinfo
def _handle(self, request): def _valid_header(name): ''' Validates if the header can be passed within the request headers. ''' # bugfix: We need to remove the "Host" from the header # since the request goes to another host, otherwise # the webserver returns a 404 because the domain is # not hosted on that server. The webserver # should add the correct Host based on the request. # This problem might not be exposed running on localhost return (name in settings.EXPOSE_HEADERS_WHITELIST or (name.startswith('CSRF_') and name not in ['CSRF_COOKIE_USED']) or (name.startswith('HTTP_') and name not in ['HTTP_HOST'])) def _get_method(request): # Fixes: # django.http.request.RawPostDataException: # You cannot access body after reading from request's data stream # # Django does not read twice the `request.body` on `POST` calls: # but it was already read while checking the CSRF token. # This raises an exception in the line below `data=request.body ...`. # The Ajax call changed it from `POST` to `PUT`, # here it's changed back to its real value. # # All the conditions are checked to avoid further issues with this workaround. if request.method == 'PUT' and request.META.get( 'HTTP_X_METHOD', '').upper() == 'POST': return 'POST' return request.method # builds request headers headers = { normalize_meta_http_name(header): str(value) for header, value in request.META.items() if _valid_header(header) and str(value) } headers = add_current_realm_in_headers(request, headers) method = _get_method(request) logger.debug(f'{method} {request.external_url}') response = exec_request( method=method, url=request.external_url, data=request.body if request.body else None, headers=headers, ) if response.status_code == 204: # NO-CONTENT http_response = HttpResponse(status=response.status_code) else: http_response = HttpResponse( content=response, status=response.status_code, content_type=response.headers.get('Content-Type'), ) # copy the exposed headers from the original response ones # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers # https://fetch.spec.whatwg.org/#http-access-control-expose-headers expose_headers = [ normalize_meta_http_name(header) for header in settings.EXPOSE_HEADERS_WHITELIST ] + response.headers.get('Access-Control-Expose-Headers', '').split(', ') for key in expose_headers: if key in response.headers: http_response[key] = response.headers[key] # wildcard if '*' in expose_headers: # include all headers but "Authorization" for key in response.headers: if key != 'Authorization': http_response[key] = response.headers[key] return http_response