Example #1
0
    def get(self):
        """
        Authenticate a Rucio account temporarily via a GSS token.

        .. :quickref: GSS; Authenticate with GSS token

        :reqheader Rucio-VO: VO name as a string (Multi-VO only).
        :reqheader Rucio-Account: Account identifier as a string.
        :reqheader Rucio-AppID: Application identifier as a string.
        :reqheader SavedCredentials: Apache mod_auth_kerb SavedCredentials.
        :resheader Access-Control-Allow-Origin:
        :resheader Access-Control-Allow-Headers:
        :resheader Access-Control-Allow-Methods:
        :resheader Access-Control-Allow-Credentials:
        :resheader Access-Control-Expose-Headers:
        :resheader X-Rucio-Auth-Token: The authentication token
        :status 200: Successfully authenticated
        :status 404: Invalid credentials
        """

        headers = self.get_headers()

        headers['Content-Type'] = 'application/octet-stream'
        headers[
            'Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
        headers.add('Cache-Control', 'post-check=0, pre-check=0')
        headers['Pragma'] = 'no-cache'

        vo = extract_vo(request.headers)
        account = request.headers.get('X-Rucio-Account', default=None)
        gsscred = request.environ.get('REMOTE_USER')
        appid = request.headers.get('X-Rucio-AppID', default='unknown')
        ip = request.headers.get('X-Forwarded-For',
                                 default=request.remote_addr)

        try:
            result = get_auth_token_gss(account, gsscred, appid, ip, vo=vo)
        except AccessDenied:
            return generate_http_error_flask(
                status_code=401,
                exc=CannotAuthenticate.__name__,
                exc_msg=
                f'Cannot authenticate to account {account} with given credentials',
                headers=headers)

        if result is None:
            return generate_http_error_flask(
                status_code=401,
                exc=CannotAuthenticate.__name__,
                exc_msg=
                f'Cannot authenticate to account {account} with given credentials',
                headers=headers)

        headers['X-Rucio-Auth-Token'] = result['token']
        headers['X-Rucio-Auth-Token-Expires'] = date_to_str(
            result['expires_at'])
        return '', 200, headers
Example #2
0
    def get(self):
        """
        Request a challenge token for SSH authentication

        .. :quickref: SSH; Request SSH Challenge Token

        :reqheader Rucio-VO: VO name as a string (Multi-VO only).
        :reqheader Rucio-Account: Account identifier as a string.
        :reqheader Rucio-AppID: Application identifier as a string.
        :resheader Access-Control-Allow-Origin:
        :resheader Access-Control-Allow-Headers:
        :resheader Access-Control-Allow-Methods:
        :resheader Access-Control-Allow-Credentials:
        :resheader Access-Control-Expose-Headers:
        :resheader X-Rucio-SSH-Challenge-Token: The SSH challenge token
        :resheader X-Rucio-SSH-Challenge-Token-Expires: The expiry time of the token
        :status 200: Successfully authenticated
        :status 404: Invalid credentials
        """
        headers = self.get_headers()

        headers['Content-Type'] = 'application/octet-stream'
        headers[
            'Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
        headers.add('Cache-Control', 'post-check=0, pre-check=0')
        headers['Pragma'] = 'no-cache'

        vo = extract_vo(request.headers)
        account = request.headers.get('X-Rucio-Account', default=None)
        appid = request.headers.get('X-Rucio-AppID', default='unknown')
        ip = request.headers.get('X-Forwarded-For',
                                 default=request.remote_addr)

        result = get_ssh_challenge_token(account, appid, ip, vo=vo)

        if not result:
            return generate_http_error_flask(
                status_code=401,
                exc=CannotAuthenticate.__name__,
                exc_msg=f'Cannot generate challenge for account {account}',
                headers=headers)

        headers['X-Rucio-SSH-Challenge-Token'] = result['token']
        headers['X-Rucio-SSH-Challenge-Token-Expires'] = date_to_str(
            result['expires_at'])
        return '', 200, headers
Example #3
0
    def get(self):
        """
        .. :quickref: OIDC;

        :status 200: OK
        :status 401: Unauthorized
        :resheader X-Rucio-Auth-Token: The authentication token
        """
        headers = self.get_headers()

        headers.set('Content-Type', 'application/octet-stream')
        headers.set('Cache-Control',
                    'no-cache, no-store, max-age=0, must-revalidate')
        headers.add('Cache-Control', 'post-check=0, pre-check=0')
        headers.set('Pragma', 'no-cache')

        vo = extract_vo(request.headers)
        account = request.headers.get('X-Rucio-Account', default=None)
        token = request.headers.get('X-Rucio-Auth-Token', default=None)
        if token is None or account is None:
            return generate_http_error_flask(401,
                                             CannotAuthorize.__name__,
                                             'Cannot authorize token request.',
                                             headers=headers)

        try:
            result = refresh_cli_auth_token(token, account, vo=vo)
        except AccessDenied:
            return generate_http_error_flask(401,
                                             CannotAuthorize.__name__,
                                             'Cannot authorize token request.',
                                             headers=headers)

        if result is not None and len(result) > 1:
            headers.set('X-Rucio-Auth-Token', str(result[0]))
            headers.set('X-Rucio-Auth-Token-Expires', str(result[1]))
        else:
            headers.set('X-Rucio-Auth-Token', '')
            headers.set('X-Rucio-Auth-Token-Expires', '')
        return '', 200, headers
Example #4
0
    def get(self, scope_name):
        """
        ---
        summary: Metalink redirect
        description: Get Metalink redirect.
        tags:
          - Redirect
        parameters:
        - name: scope_name
          in: path
          description: The data identifier (scope)/(name).
          schema:
            type: string
          style: simple
        - name: ip
          in: query
          description: The client ip.
          schema:
            type: string
          style: simple
          required: false
        - name: fqdn
          in: query
          schema:
            type: string
          style: simple
          required: false
        - name: site
          in: query
          schema:
            type: string
          style: simple
          required: false
        - name: schemes
          in: query
          schema:
            type: array
          style: simple
          required: false
        - name: select
          in: query
          schema:
            type: string
          style: simple
          required: false
        - name: sort
          in: query
          schema:
            type: string
          style: simple
          required: false
        responses:
          200:
            description: OK
            content:
              application/metalink4+xml:
                schema:
                  description: The metalink file.
                  type: string
          401:
            description: Invalid Auth Token
          404:
            description: Rse or did not found
          406:
            description: Not acceptable
        """
        headers = self.get_headers()

        try:
            scope, name = parse_scope_name(scope_name,
                                           extract_vo(request.headers))
        except ValueError as error:
            return generate_http_error_flask(400, error, headers=headers)

        # set the correct client IP
        client_ip = request.headers.get('X-Forwarded-For',
                                        default=request.remote_addr)

        client_location = {
            'ip': request.args.get('ip', default=client_ip),
            'fqdn': request.args.get('fqdn', default=None),
            'site': request.args.get('site', default=None),
        }

        dids = [{'scope': scope, 'name': name}]
        schemes = request.args.getlist('schemes') or [
            'http', 'https', 'root', 'gsiftp', 'srm', 'davs'
        ]
        sortby = request.args.get('select', default=None)
        sortby = request.args.get('sort', default=sortby)

        # get vo if given
        vo = extract_vo(request.headers)

        try:
            replicas_iter = list_replicas(dids=dids,
                                          schemes=schemes,
                                          client_location=client_location,
                                          vo=vo)
            try:
                first = next(replicas_iter)
            except StopIteration:
                return 'no redirection possible - cannot find the DID', 404

            def generate():
                # first, set the appropriate content type, and stream the header
                yield '<?xml version="1.0" encoding="UTF-8"?>\n<metalink xmlns="urn:ietf:params:xml:ns:metalink">\n'

                # iteratively stream the XML per file
                for rfile in itertools.chain((first, ), replicas_iter):
                    replicas = []
                    dictreplica = {}
                    for rse in rfile['rses']:
                        for replica in rfile['rses'][rse]:
                            replicas.append(replica)
                            dictreplica[replica] = rse

                    # stream metadata
                    yield ' <file name="' + rfile['name'] + '">\n'
                    yield '  <identity>' + rfile['scope'] + ':' + rfile[
                        'name'] + '</identity>\n'

                    if rfile['adler32'] is not None:
                        yield '  <hash type="adler32">' + rfile[
                            'adler32'] + '</hash>\n'
                    if rfile['md5'] is not None:
                        yield '  <hash type="md5">' + rfile['md5'] + '</hash>\n'

                    yield '  <size>' + str(rfile['bytes']) + '</size>\n'

                    yield f'  <glfn name="/atlas/rucio/{rfile["scope"]}:{rfile["name"]}">'
                    yield '</glfn>\n'

                    replicas = sort_replicas(dictreplica,
                                             client_location,
                                             selection=sortby)

                    # stream URLs
                    idx = 1
                    for replica in replicas:
                        yield '  <url location="' + str(
                            dictreplica[replica]) + '" priority="' + str(
                                idx) + '">' + replica + '</url>\n'
                        idx += 1

                    yield ' </file>\n'

                # don't forget to send the metalink footer
                yield '</metalink>\n'

            return try_stream(generate(),
                              content_type='application/metalink4+xml')
        except (DataIdentifierNotFound, ReplicaNotFound) as error:
            return generate_http_error_flask(404, error, headers=headers)
Example #5
0
    def get(self, scope_name):
        """
        ---
        summary: Header redirect
        description: Get the header redirect.
        tags:
          - Redirect
        parameters:
        - name: scope_name
          in: path
          description: The data identifier (scope)/(name).
          schema:
            type: string
          style: simple
        - name: ip
          in: query
          description: The client ip.
          schema:
            type: string
          style: simple
          required: false
        - name: fqdn
          in: query
          schema:
            type: string
          style: simple
          required: false
        - name: site
          in: query
          schema:
            type: string
          style: simple
          required: false
        - name: schemes
          in: query
          schema:
            type: array
          style: simple
          required: false
        - name: select
          in: query
          schema:
            type: string
          style: simple
          required: false
        - name: sort
          in: query
          schema:
            type: string
          style: simple
          required: false
        - name: rse
          in: query
          schema:
            type: string
          style: simple
          required: false
        responses:
          303:
            description: OK
            content:
              application/json:
                schema:
                  description: The redirect url.
                  type: string
          401:
            description: Invalid Auth Token
          404:
            description: Rse or did not found
        """
        headers = self.get_headers()

        try:
            scope, name = parse_scope_name(scope_name,
                                           extract_vo(request.headers))
        except ValueError as error:
            return generate_http_error_flask(400, error, headers=headers)

        try:
            client_ip = request.headers.get('X-Forwarded-For',
                                            default=request.remote_addr)

            client_location = {
                'ip': request.args.get('ip', default=client_ip),
                'fqdn': request.args.get('fqdn', default=None),
                'site': request.args.get('site', default=None),
            }
            # use the default HTTP protocols if no scheme is given
            schemes = request.args.getlist('schemes') or [
                'davs', 'https', 's3'
            ]
            sortby = request.args.get('select', default='random')
            sortby = request.args.get('sort', default=sortby)
            rse = request.args.get('rse', default=None)
            site = request.args.get('site', default=None)

            # correctly forward the schemes and select to potential metalink followups
            cleaned_url = request.environ.get('REQUEST_URI').split('?')[0]

            headers.set(
                'Link',
                f'<{cleaned_url}/metalink?schemes={",".join(schemes)}&select={sortby}>; rel=describedby; type="application/metalink+xml"'
            )

            # get vo if given
            vo = extract_vo(request.headers)

            replicas = list(
                list_replicas(dids=[{
                    'scope': scope,
                    'name': name,
                    'type': 'FILE'
                }],
                              schemes=schemes,
                              client_location=client_location,
                              vo=vo))

            selected_url = None
            for r in replicas:
                if r['rses']:
                    dictreplica = {}

                    if rse:
                        if rse in r['rses'] and r['rses'][rse]:
                            selected_url = r['rses'][rse][0]
                        else:
                            return 'no redirection possible - no valid RSE for HTTP redirection found', 404, headers
                    else:

                        for rep in r['rses']:
                            for replica in r['rses'][rep]:
                                # since this is HTTP-only redirection, and to ensure compatibility with as many http clients as possible
                                # forcibly replacement davs and s3 URLs to https
                                replica = replica.replace(
                                    'davs://',
                                    'https://').replace('s3://', 'https://')
                                dictreplica[replica] = rep

                        if not dictreplica:
                            return 'no redirection possible - no valid RSE for HTTP redirection found', 404, headers

                        elif site:
                            rep = site_selector(dictreplica, site, vo)
                            if rep:
                                selected_url = rep[0]
                            else:
                                return 'no redirection possible - no valid RSE for HTTP redirection found', 404, headers
                        else:
                            rep = sort_replicas(dictreplica,
                                                client_location,
                                                selection=sortby)
                            selected_url = rep[0]

            if selected_url:
                response = redirect(selected_url, code=303)
                response.headers.extend(headers)
                return response

            return 'no redirection possible - file does not exist', 404, headers
        except ReplicaNotFound as error:
            return generate_http_error_flask(404, error, headers=headers)
Example #6
0
    def get(self):
        """
        ---
        summary: Sign URL
        description: Sign a url for a limited lifetime for a particular srevice.
        tags:
          - Credentials
        parameters:
        - name: rse
          in: query
          description: The RSE to authenticate against.
          schema:
            type: string
          required: true
        - name: lifetime
          in: query
          description: The lifetime, default 600s.
          schema:
            type: string
          required: false
        - name: svc
          in: query
          description: The service, default gcs.
          schema:
            type: string
          required: false
        - name: op
          in: query
          description: The operation.
          schema:
            type: string
          required: false
        - name: url
          in: query
          description: The Url of the authentification.
          schema:
            type: string
          required: true
        requestBody:
          content:
            'application/octet-stream':
              schema:
                type: object
                properties:
                  X-Rucio-Account:
                    description: Account identifier.
                    type: string
                  X-Rucio-VO:
                    description: VO name (Multi-VO only).
                    type: string
                  X-Rucio-AppID:
                    description: Application identifier.
                    type: string
        responses:
          200:
            description: OK
            content:
              application/json:
                schema:
                  type: array
                  items:
                    type: object
                    description: An account attribute.
                    properties:
                      key:
                        description: The key of the account attribute.
                        type: string
                      value:
                        description: The value of the account attribute.
                        type: string
          401:
            description: Invalid Auth Token
          400:
            description: bad request, no rse or url found.
          406:
            description: Not acceptable.
        """
        headers = self.get_headers()
        vo = extract_vo(request.headers)
        account = request.headers.get('X-Rucio-Account', default=None)
        appid = request.headers.get('X-Rucio-AppID', default='unknown')
        ip = request.headers.get('X-Forwarded-For',
                                 default=request.remote_addr)

        if 'rse' not in request.args:
            return generate_http_error_flask(400,
                                             ValueError.__name__,
                                             'Parameter "rse" not found',
                                             headers=headers)
        rse = request.args.get('rse')

        lifetime = request.args.get('lifetime', type=int, default=600)
        service = request.args.get('svc', default='gcs')
        operation = request.args.get('op', default='read')

        if 'url' not in request.args:
            return generate_http_error_flask(400,
                                             ValueError.__name__,
                                             'Parameter "url" not found',
                                             headers=headers)
        url = request.args.get('url')

        if service not in ['gcs', 's3', 'swift']:
            return generate_http_error_flask(
                400,
                ValueError.__name__,
                'Parameter "svc" must be either empty(=gcs), gcs, s3 or swift',
                headers=headers)

        if operation not in ['read', 'write', 'delete']:
            return generate_http_error_flask(
                400,
                ValueError.__name__,
                'Parameter "op" must be either empty(=read), read, write, or delete.',
                headers=headers)

        result = get_signed_url(account,
                                appid,
                                ip,
                                rse=rse,
                                service=service,
                                operation=operation,
                                url=url,
                                lifetime=lifetime,
                                vo=vo)

        if not result:
            return generate_http_error_flask(
                401,
                CannotAuthenticate.__name__,
                f'Cannot generate signed URL for account {account}',
                headers=headers)

        return str(result), 200, headers
Example #7
0
    def get(self):
        """
        .. :quickref: SAML;

        :status 200: OK
        :status 401: Unauthorized
        :reqheader Rucio-VO: VO name as a string (Multi-VO only)
        :reqheader Rucio-Account: Account identifier as a string.
        :reqheader Rucio-Username: Username as a string.
        :reqheader Rucio-Password: Password as a string.
        :reqheader Rucio-AppID: Application identifier as a string.
        :resheader X-Rucio-SAML-Auth-URL: as a variable-length string header.
        """
        headers = self.get_headers()

        headers.set('Content-Type', 'application/octet-stream')
        headers.set('Cache-Control',
                    'no-cache, no-store, max-age=0, must-revalidate')
        headers.add('Cache-Control', 'post-check=0, pre-check=0')
        headers.set('Pragma', 'no-cache')

        if not EXTRA_MODULES['onelogin']:
            return "SAML not configured on the server side.", 400, headers

        saml_nameid = request.cookies.get('saml-nameid', default=None)
        vo = extract_vo(request.headers)
        account = request.headers.get('X-Rucio-Account', default=None)
        appid = request.headers.get('X-Rucio-AppID', default='unknown')
        ip = request.headers.get('X-Forwarded-For',
                                 default=request.remote_addr)

        if saml_nameid:
            try:
                result = get_auth_token_saml(account,
                                             saml_nameid,
                                             appid,
                                             ip,
                                             vo=vo)
            except AccessDenied:
                return generate_http_error_flask(
                    status_code=401,
                    exc=CannotAuthenticate.__name__,
                    exc_msg=
                    f'Cannot authenticate to account {account} with given credentials',
                    headers=headers)

            if not result:
                return generate_http_error_flask(
                    status_code=401,
                    exc=CannotAuthenticate.__name__,
                    exc_msg=
                    f'Cannot authenticate to account {account} with given credentials',
                    headers=headers)

            headers.set('X-Rucio-Auth-Token', result['token'])
            headers.set('X-Rucio-Auth-Token-Expires',
                        date_to_str(result['expires_at']))
            return '', 200, headers

        # Path to the SAML config folder
        SAML_PATH = config_get('saml', 'config_path')

        req = prepare_saml_request(request.environ,
                                   dict(request.args.items(multi=False)))
        auth = OneLogin_Saml2_Auth(req, custom_base_path=SAML_PATH)

        headers.set('X-Rucio-SAML-Auth-URL', auth.login())
        return '', 200, headers
Example #8
0
    def get(self):
        """
        Authenticate a Rucio account temporarily via SSH key exchange.

        .. :quickref: SSH; Authenticate with SSH key exchange.

        :reqheader Rucio-VO: VO name as a string (Multi-VO only).
        :reqheader Rucio-Account: Account identifier as a string.
        :reqheader Rucio-SSH-Signature: Response to server challenge signed with SSH private key as a base64 encoded string.
        :reqheader Rucio-AppID: Application identifier as a string.
        :resheader Access-Control-Allow-Origin:
        :resheader Access-Control-Allow-Headers:
        :resheader Access-Control-Allow-Methods:
        :resheader Access-Control-Allow-Credentials:
        :resheader Access-Control-Expose-Headers:
        :resheader X-Rucio-Auth-Token: The authentication token
        :status 200: Successfully authenticated
        :status 404: Invalid credentials
        """
        headers = self.get_headers()

        headers['Content-Type'] = 'application/octet-stream'
        headers[
            'Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
        headers.add('Cache-Control', 'post-check=0, pre-check=0')
        headers['Pragma'] = 'no-cache'

        vo = extract_vo(request.headers)
        account = request.headers.get('X-Rucio-Account', default=None)
        signature = request.headers.get('X-Rucio-SSH-Signature', default=None)
        appid = request.headers.get('X-Rucio-AppID', default='unknown')
        ip = request.headers.get('X-Forwarded-For',
                                 default=request.remote_addr)

        # decode the signature which must come in base64 encoded
        try:
            signature += '=' * (
                (4 - len(signature) % 4) % 4)  # adding required padding
            signature = base64.b64decode(signature)
        except TypeError:
            return generate_http_error_flask(
                status_code=401,
                exc=CannotAuthenticate.__name__,
                exc_msg=
                f'Cannot authenticate to account {account} with malformed signature',
                headers=headers)

        try:
            result = get_auth_token_ssh(account, signature, appid, ip, vo=vo)
        except AccessDenied:
            return generate_http_error_flask(
                status_code=401,
                exc=CannotAuthenticate.__name__,
                exc_msg=
                f'Cannot authenticate to account {account} with given credentials',
                headers=headers)

        if not result:
            return generate_http_error_flask(
                status_code=401,
                exc=CannotAuthenticate.__name__,
                exc_msg=
                f'Cannot authenticate to account {account} with given credentials',
                headers=headers)

        headers['X-Rucio-Auth-Token'] = result['token']
        headers['X-Rucio-Auth-Token-Expires'] = date_to_str(
            result['expires_at'])
        return '', 200, headers
Example #9
0
    def get(self):
        """
        Authenticate a Rucio account temporarily via username and password.

        .. :quickref: UserPass; Authenticate with username/password

        :reqheader X-Rucio-VO: VO name as a string (Multi-VO Only)
        :reqheader X-Rucio-Account: Account identifier as a string.
        :reqheader X-Rucio-Username: Username as a string.
        :reqheader X-Rucio-Password: password as a text-plain string.
        :reqheader X-Rucio-AppID: Application identifier as a string.
        :resheader Access-Control-Allow-Origin:
        :resheader Access-Control-Allow-Headers:
        :resheader Access-Control-Allow-Methods:
        :resheader Access-Control-Allow-Credentials:
        :resheader Access-Control-Expose-Headers:
        :resheader X-Rucio-Auth-Token: The authentication token
        :status 200: Successfully authenticated
        :status 404: Invalid credentials
        """
        headers = self.get_headers()

        headers['Content-Type'] = 'application/octet-stream'
        headers[
            'Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
        headers.add('Cache-Control', 'post-check=0, pre-check=0')
        headers['Pragma'] = 'no-cache'

        vo = extract_vo(request.headers)
        account = request.headers.get('X-Rucio-Account', default=None)
        username = request.headers.get('X-Rucio-Username', default=None)
        password = request.headers.get('X-Rucio-Password', default=None)
        appid = request.headers.get('X-Rucio-AppID', default='unknown')
        ip = request.headers.get('X-Forwarded-For',
                                 default=request.remote_addr)

        if not account or not username or not password:
            return generate_http_error_flask(
                401,
                CannotAuthenticate.__name__,
                'Cannot authenticate without passing all required arguments',
                headers=headers)

        try:
            result = get_auth_token_user_pass(account,
                                              username,
                                              password,
                                              appid,
                                              ip,
                                              vo=vo)
        except AccessDenied:
            return generate_http_error_flask(
                401,
                CannotAuthenticate.__name__,
                f'Cannot authenticate to account {account} with given credentials',
                headers=headers)

        if not result:
            return generate_http_error_flask(
                401,
                CannotAuthenticate.__name__,
                f'Cannot authenticate to account {account} with given credentials',
                headers=headers)

        headers['X-Rucio-Auth-Token'] = result['token']
        headers['X-Rucio-Auth-Token-Expires'] = date_to_str(
            result['expires_at'])
        return '', 200, headers
Example #10
0
    def get(self):
        """
        Authenticate a Rucio account temporarily via an x509 certificate.

        .. :quickref: x509; Authenticate with x509 certificate.

        :reqheader Rucio-VO: VO name as a string (Multi-VO only).
        :reqheader Rucio-Account: Account identifier as a string.
        :reqheader Rucio-AppID: Application identifier as a string.
        :reqheader SSLStdEnv: Apache mod_ssl SSL Standard Env Variables.
        :resheader Access-Control-Allow-Origin:
        :resheader Access-Control-Allow-Headers:
        :resheader Access-Control-Allow-Methods:
        :resheader Access-Control-Allow-Credentials:
        :resheader Access-Control-Expose-Headers:
        :resheader X-Rucio-Auth-Token: The authentication token
        :status 200: Successfully authenticated
        :status 404: Invalid credentials
        """
        headers = self.get_headers()

        headers['Content-Type'] = 'application/octet-stream'
        headers[
            'Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
        headers.add('Cache-Control', 'post-check=0, pre-check=0')
        headers['Pragma'] = 'no-cache'

        vo = extract_vo(request.headers)
        account = request.headers.get('X-Rucio-Account', default=None)
        dn = request.environ.get('SSL_CLIENT_S_DN')
        if not dn:
            return generate_http_error_flask(401,
                                             CannotAuthenticate.__name__,
                                             'Cannot get DN',
                                             headers=headers)
        if not dn.startswith('/'):
            dn = '/' + '/'.join(dn.split(',')[::-1])

        appid = request.headers.get('X-Rucio-AppID', default='unknown')
        ip = request.headers.get('X-Forwarded-For',
                                 default=request.remote_addr)

        # If we get a valid proxy certificate we have to strip this postfix,
        # otherwise we would have to store the proxy DN in the database as well.
        # Alternative: use the SSL_CLIENT_I_DN, but that would require a separate
        # endpoint as you cannot programmatically decide, by examining the SSL variables,
        # if you got a proxy or regular certificate
        while True:
            if dn.endswith('/CN=limited proxy'):
                dn = dn[:-17]
            elif dn.endswith('/CN=proxy'):
                dn = dn[:-9]
            elif search('/CN=[0-9]*$', dn):
                dn = dn.rpartition('/')[0]
            else:
                break

        try:
            result = get_auth_token_x509(account, dn, appid, ip, vo=vo)
        except AccessDenied:
            return generate_http_error_flask(
                status_code=401,
                exc=CannotAuthenticate.__name__,
                exc_msg=
                f'Cannot authenticate to account {account} with given credentials',
                headers=headers)
        except IdentityError:
            return generate_http_error_flask(
                status_code=401,
                exc=CannotAuthenticate.__name__,
                exc_msg=f'No default account set for {dn}',
                headers=headers)

        if not result:
            return generate_http_error_flask(
                status_code=401,
                exc=CannotAuthenticate.__name__,
                exc_msg=
                f'Cannot authenticate to account {account} with given credentials',
                headers=headers)

        headers['X-Rucio-Auth-Token'] = result['token']
        headers['X-Rucio-Auth-Token-Expires'] = date_to_str(
            result['expires_at'])
        return '', 200, headers
Example #11
0
    def get(self):
        """

        .. :quickref: OIDC; Authenticate with OIDC

        :status 200: OK
        :status 401: Unauthorized
        :resheader X-Rucio-OIDC-Auth-URL: User & Rucio OIDC Client specific Authorization URL
        """
        headers = self.get_headers()

        headers.set('Content-Type', 'application/octet-stream')
        headers.set('Cache-Control',
                    'no-cache, no-store, max-age=0, must-revalidate')
        headers.add('Cache-Control', 'post-check=0, pre-check=0')
        headers.set('Pragma', 'no-cache')

        vo = extract_vo(request.headers)
        account = request.environ.get('HTTP_X_RUCIO_ACCOUNT', 'webui')
        auth_scope = request.environ.get('HTTP_X_RUCIO_CLIENT_AUTHORIZE_SCOPE',
                                         "")
        audience = request.environ.get(
            'HTTP_X_RUCIO_CLIENT_AUTHORIZE_AUDIENCE', "")
        auto = request.environ.get('HTTP_X_RUCIO_CLIENT_AUTHORIZE_AUTO', False)
        issuer = request.environ.get('HTTP_X_RUCIO_CLIENT_AUTHORIZE_ISSUER',
                                     None)
        polling = request.environ.get('HTTP_X_RUCIO_CLIENT_AUTHORIZE_POLLING',
                                      False)
        refresh_lifetime = request.environ.get(
            'HTTP_X_RUCIO_CLIENT_AUTHORIZE_REFRESH_LIFETIME', None)
        auto = (auto == 'True' or auto == 'true')
        polling = (polling == 'True' or polling == 'true')
        if refresh_lifetime == 'None':
            refresh_lifetime = None
        ip = request.headers.get('X-Forwarded-For',
                                 default=request.remote_addr)
        try:
            kwargs = {
                'auth_scope': auth_scope,
                'audience': audience,
                'issuer': issuer,
                'auto': auto,
                'polling': polling,
                'refresh_lifetime': refresh_lifetime,
                'ip': ip
            }
            result = get_auth_oidc(account, vo=vo, **kwargs)
        except AccessDenied:
            return generate_http_error_flask(
                status_code=401,
                exc=CannotAuthenticate.__name__,
                exc_msg=
                f'Cannot get authentication URL from Rucio Authentication Server for account {account}',
                headers=headers)

        if not result:
            return generate_http_error_flask(
                status_code=401,
                exc=CannotAuthenticate.__name__,
                exc_msg=
                f'Cannot get authentication URL from Rucio Authentication Server for account {account}',
                headers=headers)

        headers.set('X-Rucio-OIDC-Auth-URL', result)
        return '', 200, headers
Example #12
0
    def get(self):
        """
        Sign a URL for a limited lifetime for a particular service.

        :reqheader X-Rucio-VO: VO name as a string (Multi-VO only).
        :reqheader X-Rucio-Account: Account identifier as a string.
        :reqheader X-Rucio-AppID: Application identifier as a string.
        :status 200: Successfully signed URL
        :status 400: Bad Request
        :status 401: Unauthorized
        :status 406: Not Acceptable
        """
        headers = self.get_headers()
        vo = extract_vo(request.headers)
        account = request.headers.get('X-Rucio-Account', default=None)
        appid = request.headers.get('X-Rucio-AppID', default='unknown')
        ip = request.headers.get('X-Forwarded-For',
                                 default=request.remote_addr)

        if 'rse' not in request.args:
            return generate_http_error_flask(400,
                                             ValueError.__name__,
                                             'Parameter "rse" not found',
                                             headers=headers)
        rse = request.args.get('rse')

        lifetime = request.args.get('lifetime', type=int, default=600)
        service = request.args.get('svc', default='gcs')
        operation = request.args.get('op', default='read')

        if 'url' not in request.args:
            return generate_http_error_flask(400,
                                             ValueError.__name__,
                                             'Parameter "url" not found',
                                             headers=headers)
        url = request.args.get('url')

        if service not in ['gcs', 's3', 'swift']:
            return generate_http_error_flask(
                400,
                ValueError.__name__,
                'Parameter "svc" must be either empty(=gcs), gcs, s3 or swift',
                headers=headers)

        if operation not in ['read', 'write', 'delete']:
            return generate_http_error_flask(
                400,
                ValueError.__name__,
                'Parameter "op" must be either empty(=read), read, write, or delete.',
                headers=headers)

        result = get_signed_url(account,
                                appid,
                                ip,
                                rse=rse,
                                service=service,
                                operation=operation,
                                url=url,
                                lifetime=lifetime,
                                vo=vo)

        if not result:
            return generate_http_error_flask(
                401,
                CannotAuthenticate.__name__,
                f'Cannot generate signed URL for account {account}',
                headers=headers)

        return str(result), 200, headers