debug:
  enabled: true
hub:
  config:
    Authenticator:
      auto_login: true
      enable_auth_state: true
    JupyterHub:
      tornado_settings:
        headers: { 'Content-Security-Policy': "frame-ancestors *;" }
  db:
    pvc:
      storageClassName: csi-sc-cinderplugin
  extraConfig:
    oauthCode: |
      import time
      import requests
      from datetime import datetime
      from oauthenticator.generic import GenericOAuthenticator
      token_url = 'https://' + os.environ['NEXTCLOUD_HOST'] + '/index.php/apps/oauth2/api/v1/token'
      debug = os.environ.get('NEXTCLOUD_DEBUG_OAUTH', 'false').lower() in ['true', '1', 'yes']

      def get_nextcloud_access_token(refresh_token):
        client_id = os.environ['NEXTCLOUD_CLIENT_ID']
        client_secret = os.environ['NEXTCLOUD_CLIENT_SECRET']

        code = refresh_token
        data = {
          'grant_type': 'refresh_token',
          'code': code,
          'refresh_token': refresh_token,
          'client_id': client_id,
          'client_secret': client_secret
        }
        response = requests.post(token_url, data=data)
        if debug:
          print(response.text)
        return response.json()

      def post_auth_hook(authenticator, handler, authentication):
        user = authentication['auth_state']['oauth_user']['ocs']['data']['id']
        auth_state = authentication['auth_state']
        auth_state['token_expires'] =  time.time() + auth_state['token_response']['expires_in']
        authentication['auth_state'] = auth_state
        return authentication

      class NextcloudOAuthenticator(GenericOAuthenticator):
        def __init__(self, *args, **kwargs):
          super().__init__(*args, **kwargs)
          self.user_dict = {}

        async def pre_spawn_start(self, user, spawner):
          super().pre_spawn_start(user, spawner)
          auth_state = await user.get_auth_state()
          if not auth_state:
            return
          access_token = auth_state['access_token']
          spawner.environment['NEXTCLOUD_ACCESS_TOKEN'] = access_token

        async def refresh_user(self, user, handler=None):
          auth_state = await user.get_auth_state()
          if not auth_state:
            if debug:
              print(f'auth_state missing for {user}')
            return False
          access_token = auth_state['access_token']
          refresh_token = auth_state['refresh_token']
          token_response = auth_state['token_response']
          now = time.time()
          now_hr = datetime.fromtimestamp(now)
          expires = auth_state['token_expires']
          expires_hr = datetime.fromtimestamp(expires)
          expires = 0
          if debug:
            print(f'auth_state for {user}: {auth_state}')
          if now >= expires:
            if debug:
              print(f'Time is: {now_hr}, token expired: {expires_hr}')
              print(f'Refreshing token for {user}')
            try:
              token_response = get_nextcloud_access_token(refresh_token)
              auth_state['access_token'] = token_response['access_token']
              auth_state['refresh_token'] = token_response['refresh_token']
              auth_state['token_expires'] = now + token_response['expires_in']
              auth_state['token_response'] = token_response
              if debug:
                print(f'Successfully refreshed token for {user.name}')
                print(f'auth_state for {user.name}: {auth_state}')
              return {'name': user.name, 'auth_state': auth_state}
            except Exception as e:
              if debug:
                print(f'Failed to refresh token for {user}')
              return False
            return False
          if debug:
            print(f'Time is: {now_hr}, token expires: {expires_hr}')
          return True

      c.JupyterHub.authenticator_class = NextcloudOAuthenticator
      c.NextcloudOAuthenticator.client_id = os.environ['NEXTCLOUD_CLIENT_ID']
      c.NextcloudOAuthenticator.client_secret = os.environ['NEXTCLOUD_CLIENT_SECRET']
      c.NextcloudOAuthenticator.login_service = 'Sunet Drive'
      c.NextcloudOAuthenticator.username_claim = lambda r: r.get('ocs', {}).get('data', {}).get('id')
      c.NextcloudOAuthenticator.userdata_url = 'https://' + os.environ['NEXTCLOUD_HOST'] + '/ocs/v2.php/cloud/user?format=json'
      c.NextcloudOAuthenticator.authorize_url = 'https://' + os.environ['NEXTCLOUD_HOST'] + '/index.php/apps/oauth2/authorize'
      c.NextcloudOAuthenticator.token_url = token_url
      c.NextcloudOAuthenticator.oauth_callback_url = 'https://' + os.environ['JUPYTER_HOST'] + '/hub/oauth_callback'
      c.NextcloudOAuthenticator.allow_all = True
      c.NextcloudOAuthenticator.refresh_pre_spawn = True
      c.NextcloudOAuthenticator.enable_auth_state = True
      c.NextcloudOAuthenticator.auth_refresh_age = 3600
      c.NextcloudOAuthenticator.post_auth_hook = post_auth_hook

    serviceCode: |
      import sys
      c.JupyterHub.load_roles = [
          {
              "name": "refresh-token",
              "services": [
                "refresh-token"
              ],
              "scopes": [
                "read:users",
                "admin:auth_state"
              ]
          },
          {
              "name": "user",
              "scopes": [
                "access:services!service=refresh-token",
                "read:services!service=refresh-token",
                "self",
              ],
          },
          {
              "name": "server",
              "scopes": [
                "access:services!service=refresh-token",
                "read:services!service=refresh-token",
                "inherit",
              ],
          }
      ]
      c.JupyterHub.services = [
          {
              'name': 'refresh-token',
              'url': 'http://' + os.environ.get('HUB_SERVICE_HOST', 'hub') + ':' + os.environ.get('HUB_SERVICE_PORT_REFRESH_TOKEN', '8082'),
              'display': False,
              'oauth_no_confirm': True,
              'api_token': os.environ['JUPYTERHUB_API_KEY'],
              'command': [sys.executable, '/usr/local/etc/jupyterhub/refresh-token.py']
          }
      ]
      c.JupyterHub.admin_users = {"refresh-token"}
      c.JupyterHub.api_tokens = {
          os.environ['JUPYTERHUB_API_KEY']: "refresh-token",
      }
  extraFiles:
    refresh-token.py: 
      mountPath: /usr/local/etc/jupyterhub/refresh-token.py
      stringData: |
        """A token refresh service authenticating with the Hub.

        This service serves `/services/refresh-token/`,
        authenticated with the Hub,
        showing the user their own info.
        """
        import json
        import os
        import requests
        import socket
        from jupyterhub.services.auth import HubAuthenticated
        from jupyterhub.utils import url_path_join
        from tornado.httpserver import HTTPServer
        from tornado.ioloop import IOLoop
        from tornado.web import Application, HTTPError, RequestHandler, authenticated
        from urllib.parse import urlparse
        debug = os.environ.get('NEXTCLOUD_DEBUG_OAUTH', 'false').lower() in ['true', '1', 'yes']
        def my_debug(s):
          if debug:
            with open("/proc/1/fd/1", "a") as stdout:
              print(s, file=stdout)


        class RefreshHandler(HubAuthenticated, RequestHandler):
            def api_request(self, method, url, **kwargs):
                my_debug(f'{self.hub_auth}')
                url = url_path_join(self.hub_auth.api_url, url)
                allow_404 = kwargs.pop('allow_404', False)
                headers = kwargs.setdefault('headers', {})
                headers.setdefault('Authorization', f'token {self.hub_auth.api_token}')
                try:
                    r = requests.request(method, url, **kwargs)
                except requests.ConnectionError as e:
                    my_debug(f'Error connecting to {url}: {e}')
                    msg = f'Failed to connect to Hub API at {url}.'
                    msg += f'  Is the Hub accessible at this URL (from host: {socket.gethostname()})?'

                    if '127.0.0.1' in url:
                        msg += '  Make sure to set c.JupyterHub.hub_ip to an IP accessible to' + \
                               ' single-user servers if the servers are not on the same host as the Hub.'
                    raise HTTPError(500, msg)

                data = None
                if r.status_code == 404 and allow_404:
                    pass
                elif r.status_code == 403:
                    my_debug(
                        'Lacking permission to check authorization with JupyterHub,' + 
                        f' my auth token may have expired: [{r.status_code}] {r.reason}'
                    )
                    my_debug(r.text)
                    raise HTTPError(
                        500,
                        'Permission failure checking authorization, I may need a new token'
                    )
                elif r.status_code >= 500:
                    my_debug(f'Upstream failure verifying auth token: [{r.status_code}] {r.reason}')
                    my_debug(r.text)
                    raise HTTPError(
                        502, 'Failed to check authorization (upstream problem)')
                elif r.status_code >= 400:
                    my_debug(f'Failed to check authorization: [{r.status_code}] {r.reason}')
                    my_debug(r.text)
                    raise HTTPError(500, 'Failed to check authorization')
                else:
                    data = r.json()
                return data

            @authenticated
            def get(self):
                user_model = self.get_current_user()
                # Fetch current auth state
                user_data = self.api_request('GET', url_path_join('users', user_model['name']))
                auth_state = user_data['auth_state']
                access_token = auth_state['access_token']
                token_expires = auth_state['token_expires']

                self.set_header('content-type', 'application/json')
                self.write(json.dumps({'access_token': access_token, 'token_expires': token_expires}, indent=1, sort_keys=True))

        class PingHandler(RequestHandler):

            def get(self):
                my_debug(f"DEBUG: In ping get")
                self.set_header('content-type', 'application/json')
                self.write(json.dumps({'ping': 1}))


        def main():
            app = Application([
                (os.environ['JUPYTERHUB_SERVICE_PREFIX'] + 'tokens', RefreshHandler),
                (os.environ['JUPYTERHUB_SERVICE_PREFIX'] + '/?', PingHandler),
            ])

            http_server = HTTPServer(app)
            url = urlparse(os.environ['JUPYTERHUB_SERVICE_URL'])

            http_server.listen(url.port)

            IOLoop.current().start()

        if __name__ == '__main__':
            main()
  networkPolicy:
    ingress:
      - ports:
          - port: 8082
        from:
          - podSelector:
              matchLabels:
                hub.jupyter.org/network-access-hub: "true"
  service:
    extraPorts:
      - port: 8082
        targetPort: 8082
        name: refresh-token
  extraEnv:
    NEXTCLOUD_DEBUG_OAUTH: "no"
    NEXTCLOUD_HOST: sunet.drive.test.sunet.se
    JUPYTER_HOST: jupyter.drive.test.sunet.se
    JUPYTERHUB_API_KEY:
      valueFrom:
        secretKeyRef:
          name: jupyterhub-secrets
          key: api-key
    JUPYTERHUB_CRYPT_KEY:
      valueFrom:
        secretKeyRef:
          name: jupyterhub-secrets
          key: crypt-key
    NEXTCLOUD_CLIENT_ID:
      valueFrom:
        secretKeyRef:
          name: nextcloud-oauth-secrets
          key: client-id
    NEXTCLOUD_CLIENT_SECRET:
      valueFrom:
        secretKeyRef:
          name: nextcloud-oauth-secrets
          key: client-secret
    networkPolicy:
      enabled: false
proxy:
  chp:
    networkPolicy:
      egress:
        - to:
            - podSelector:
                matchLabels:
                  app: jupyterhub
                  component: hub
          ports:
            - port: 8082
singleuser:
  image:
    name: docker.sunet.se/drive/jupyter-custom
    tag: lab-4.0.10-sunet3
  storage:
    dynamic:
      storageClass: csi-sc-cinderplugin
  extraEnv:
    JUPYTER_ENABLE_LAB: "yes"
  extraFiles:
    jupyter_notebook_config:
      mountPath: /home/jovyan/.jupyter/jupyter_server_config.py
      stringData: |
        import os
        c = get_config()
        c.NotebookApp.allow_origin = '*'
        c.NotebookApp.tornado_settings = {
            'headers': { 'Content-Security-Policy': "frame-ancestors *;" }
        }
        os.system('/usr/local/bin/nc-sync')
      mode: 0644
