Exemple #1
0
        def wrapper(*args, **kwargs):
            try:
                user = None
                if current_user.is_authenticated:
                    user = _get_user_from_invenio_user(current_user.email)
                elif include_gitlab_login and "X-Gitlab-Token" in request.headers:
                    user = get_user_from_token(request.headers["X-Gitlab-Token"])
                elif "access_token" in request.args:
                    user = get_user_from_token(request.args.get("access_token"))
                if not user:
                    return jsonify(message="User not signed in"), 401
            except ValueError as e:
                logging.error(traceback.format_exc())
                return jsonify({"message": str(e)}), 403

            return func(*args, **kwargs, user=user)
Exemple #2
0
def gitlab_projects():  # noqa
    r"""Endpoint to retrieve GitLab projects.
    ---
    get:
      summary: Get user project from GitLab
      operationId: gitlab_projects
      description: >-
        Retrieve projects from GitLab.
      produces:
       - application/json
      responses:
        200:
          description: >-
            This resource return all projects owned by
            the user on GitLab in JSON format.
        403:
          description: >-
            Request failed. User token not valid.
          examples:
            application/json:
              {
                "message": "Token is not valid."
              }
        500:
          description: >-
            Request failed. Internal controller error.
    """
    try:
        if current_user.is_authenticated:
            user = _get_user_from_invenio_user(current_user.email)
        else:
            user = get_user_from_token(request.args.get("access_token"))
        secrets_store = REANAUserSecretsStore(str(user.id_))
        gitlab_token = secrets_store.get_secret_value("gitlab_access_token")
        gitlab_user = secrets_store.get_secret_value("gitlab_user")
        gitlab_url = REANA_GITLAB_URL + "/api/v4/users/{0}/projects?access_token={1}"
        response = requests.get(gitlab_url.format(gitlab_user, gitlab_token))
        projects = dict()
        if response.status_code == 200:
            for gitlab_project in response.json():
                hook_id = _get_gitlab_hook_id(response, gitlab_project["id"],
                                              gitlab_token)
                projects[gitlab_project["id"]] = {
                    "name": gitlab_project["name"],
                    "path": gitlab_project["path_with_namespace"],
                    "url": gitlab_project["web_url"],
                    "hook_id": hook_id,
                }
            return jsonify(projects), 200
        else:
            return (
                jsonify({"message": "Project list could not be retrieved"}),
                response.status_code,
            )
    except ValueError:
        return jsonify({"message": "Token is not valid."}), 403
    except Exception as e:
        logging.error(traceback.format_exc())
        return jsonify({"message": str(e)}), 500
Exemple #3
0
def gitlab_webhook():  # noqa
    r"""Endpoint to setup a GitLab webhook.
    ---
    post:
      summary: Set a webhook on a user project from GitLab
      operationId: gitlab_webhook
      description: >-
        Setup a webhook for a GitLab project on GitLab.
      produces:
       - application/json
      responses:
        201:
          description: >-
            The webhook was created.
        403:
          description: >-
            Request failed. User token not valid.
          examples:
            application/json:
              {
                "message": "Token is not valid."
              }
        500:
          description: >-
            Request failed. Internal controller error.
    """
    try:
        if current_user.is_authenticated:
            user = _get_user_from_invenio_user(current_user.email)
        else:
            user = get_user_from_token(request.args.get('access_token'))
        secrets_store = REANAUserSecretsStore(str(user.id_))
        gitlab_token = secrets_store.get_secret_value('gitlab_access_token')
        parameters = request.json
        gitlab_url = REANA_GITLAB_URL + "/api/v4/projects/" + \
            "{0}/hooks?access_token={1}"
        webhook_payload = {
            "url": "https://{}/api/workflows".format(REANA_URL),
            "push_events": True,
            "push_events_branch_filter": "master",
            "merge_requests_events": True,
            "enable_ssl_verification": False,
            "token": user.access_token,
        }
        webhook = requests.post(gitlab_url.format(
                                              parameters['project_id'],
                                              gitlab_token),
                                data=webhook_payload)
        return webhook.content, 201
    except ValueError:
        return jsonify({"message": "Token is not valid."}), 403
    except Exception as e:
        logging.error(traceback.format_exc())
        return jsonify({"message": str(e)}), 500
Exemple #4
0
def get_me():
    r"""Endpoint to get user information.

    ---
    get:
      summary: Gets information about authenticated user.
      description: >-
        This resource provides basic information about an authenticated
        user based on the session cookie presence.
      operationId: get_me
      produces:
        - application/json
      parameters:
        - name: access_token
          in: query
          description: API access_token of user.
          required: false
          type: string
      responses:
        200:
          description: >-
            User information correspoding to the session cookie sent
            in the request.
          schema:
            type: object
            properties:
              email:
                type: string
              reana_token:
                type: string
          examples:
            application/json:
              {
                "email": "*****@*****.**",
                "reana_token": "Drmhze6EPcv0fN_81Bj-nA"
              }
        401:
          description: >-
            Error message indicating that the uses is not authenticated.
          schema:
            type: object
            properties:
              error:
                type: string
          examples:
            application/json:
              {
                "error": "User not logged in"
              }
        403:
          description: >-
            Request failed. User token not valid.
          examples:
            application/json:
              {
                "message": "Token is not valid."
              }
        500:
          description: >-
            Request failed. Internal server error.
          examples:
            application/json:
              {
                "message": "Internal server error."
              }
    """
    try:
        me = None
        if current_user.is_authenticated:
            me = _get_user_from_invenio_user(current_user.email)
        elif "access_token" in request.args:
            me = get_user_from_token(request.args.get('access_token'))

        if me:
            return (jsonify({
                'email': me.email,
                'reana_token': me.access_token,
                'full_name': me.full_name,
                'username': me.username
            }), 200)
        else:
            return jsonify(message='User not logged in'), 401
    except HTTPError as e:
        logging.error(traceback.format_exc())
        return jsonify(e.response.json()), e.response.status_code
    except ValueError as e:
        logging.error(traceback.format_exc())
        return jsonify({"message": str(e)}), 403
    except Exception as e:
        logging.error(traceback.format_exc())
        return jsonify({"message": str(e)}), 500
Exemple #5
0
def add_secrets():  # noqa
    r"""Endpoint to create user secrets.

    ---
    post:
      summary: Add user secrets to REANA.
      description: >-
        This resource adds secrets for the authenticated user.
      operationId: add_secrets
      produces:
        - application/json
      parameters:
        - name: access_token
          in: query
          description: Secrets owner access token.
          required: false
          type: string
        - name: overwrite
          in: query
          description: Whether existing secret keys should be overwritten.
          required: false
          type: boolean
        - name: secrets
          in: body
          description: >-
            Optional. List of secrets to be added.
          required: true
          schema:
            type: object
            additionalProperties:
              type: object
              description: Secret definition.
              properties:
                name:
                  type: string
                  description: Secret name
                value:
                  type: string
                  description: Secret value
                type:
                  type: string
                  enum:
                    - env
                    - file
                  description: >-
                    How will be the secret assigned to the jobs, either
                    exported as an environment variable or mounted as a file.
      responses:
        201:
          description: >-
            Secrets successfully added.
          schema:
            type: object
            properties:
              message:
                type: string
          examples:
            application/json:
              {
                "message": "Secret(s) successfully added."
              }
        403:
          description: >-
            Request failed. Token is not valid.
          examples:
            application/json:
              {
                "message": "Token is not valid"
              }
        409:
          description: >-
            Request failed. Secrets could not be added due to a conflict.
          examples:
            application/json:
              {
                "message": "The submitted secrets api_key, password,
                            username already exist."
              }
        500:
          description: >-
            Request failed. Internal server error.
          examples:
            application/json:
              {
                "message": "Internal server error."
              }
    """
    try:
        if current_user.is_authenticated:
            user = _get_user_from_invenio_user(current_user.email)
        else:
            user = get_user_from_token(request.args.get('access_token'))
        secrets_store = REANAUserSecretsStore(str(user.id_))
        overwrite = json.loads(request.args.get('overwrite'))
        secrets_store.add_secrets(request.json, overwrite=overwrite)
        return jsonify({"message": "Secret(s) successfully added."}), 201
    except REANASecretAlreadyExists as e:
        return jsonify({"message": str(e)}), 409
    except ValueError:
        return jsonify({"message": "Token is not valid."}), 403
    except Exception as e:
        logging.error(traceback.format_exc())
        return jsonify({"message": str(e)}), 500
Exemple #6
0
def delete_secrets():  # noqa
    r"""Endpoint to delete user secrets.

    ---
    delete:
      summary: Deletes the specified secret(s).
      description: >-
        This resource deletes the requested secrets.
      operationId: delete_secrets
      produces:
        - application/json
      parameters:
        - name: access_token
          in: query
          description: API key of the admin.
          required: false
          type: string
        - name: secrets
          in: body
          description: >-
            Optional. List of secrets to be deleted.
          required: true
          schema:
            type: array
            description: List of secret names to be deleted.
            items:
              type: string
              description: Secret name to be deleted.
      responses:
        200:
          description: >-
            Secrets successfully deleted.
          schema:
            type: array
            description: List of secret names that have been deleted.
            items:
              type: string
              description: Name of the secret that have been deleted.
          examples:
            application/json:
              [
                ".keytab",
                "username",
              ]
        403:
          description: >-
            Request failed. Token is not valid.
          examples:
            application/json:
              {
                "message": "Token is not valid"
              }
        404:
          description: >-
            Request failed. Secrets do not exist.
          schema:
            type: array
            description: List of secret names that could not be deleted.
            items:
              type: string
              description: Name of the secret which does not exist.
          examples:
            application/json:
              [
                "certificate.pem",
                "PASSWORD",
              ]
        500:
          description: >-
            Request failed. Internal server error.
          examples:
            application/json:
              {
                "message": "Internal server error."
              }
    """
    try:
        if current_user.is_authenticated:
            user = _get_user_from_invenio_user(current_user.email)
        else:
            user = get_user_from_token(request.args.get('access_token'))
        secrets_store = REANAUserSecretsStore(str(user.id_))
        deleted_secrets_list = secrets_store.delete_secrets(request.json)
        return jsonify(deleted_secrets_list), 200
    except REANASecretDoesNotExist as e:
        return jsonify(e.missing_secrets_list), 404
    except ValueError:
        return jsonify({"message": "Token is not valid."}), 403
    except Exception as e:
        logging.error(traceback.format_exc())
        return jsonify({"message": str(e)}), 500
Exemple #7
0
def get_secrets():  # noqa
    r"""Endpoint to retrieve user secrets.

    ---
    get:
      summary: Get user secrets. Requires an user access token.
      description: >-
        Get user secrets.
      operationId: get_secrets
      produces:
        - application/json
      parameters:
        - name: access_token
          in: query
          description: Secrets owner access token.
          required: false
          type: string
      responses:
        200:
          description: >-
            List of user secrets.
          schema:
            type: array
            items:
              properties:
                name:
                  type: string
                  description: Secret name
                type:
                  type: string
                  enum:
                    - env
                    - file
                  description: >-
                    How will be the secret assigned to
                    the jobs, either exported as an environment
                    variable or mounted as a file.
          examples:
            application/json:
              [
                {
                  "name": ".keytab",
                  "value": "SGVsbG8gUkVBTkEh",
                },
                {
                  "name": "username",
                  "value": "reanauser",
                },
              ]
        403:
          description: >-
            Request failed. Token is not valid.
          examples:
            application/json:
              {
                "message": "Token is not valid"
              }
        500:
          description: >-
            Request failed. Internal server error.
          examples:
            application/json:
              {
                "message": "Error while querying."
              }
    """
    try:
        if current_user.is_authenticated:
            user = _get_user_from_invenio_user(current_user.email)
        else:
            user = get_user_from_token(request.args.get('access_token'))
        secrets_store = REANAUserSecretsStore(str(user.id_))
        user_secrets = secrets_store.get_secrets()
        return jsonify(user_secrets), 200
    except ValueError:
        return jsonify({"message": "Token is not valid."}), 403
    except Exception as e:
        logging.error(traceback.format_exc())
        return jsonify({"message": str(e)}), 500
Exemple #8
0
def gitlab_oauth():  # noqa
    r"""Endpoint to authorize REANA on GitLab.
    ---
    get:
      summary: Get access token from GitLab
      operationId: gitlab_oauth
      description: >-
        Authorize REANA on GitLab.
      produces:
       - application/json
      responses:
        200:
          description: >-
            Ping succeeded.
          schema:
            type: object
            properties:
              message:
                type: string
              status:
                type: string
          examples:
            application/json:
              message: OK
              status: 200
        201:
          description: >-
            Authorization succeeded. GitLab secret created.
          schema:
            type: object
            properties:
              message:
                type: string
              status:
                type: string
          examples:
            application/json:
              message: GitLab secret created
              status: 201
        403:
          description: >-
            Request failed. User token not valid.
          examples:
            application/json:
              {
                "message": "Token is not valid."
              }
        500:
          description: >-
            Request failed. Internal controller error.
    """
    try:
        if current_user.is_authenticated:
            user = _get_user_from_invenio_user(current_user.email)
        else:
            user = get_user_from_token(request.args.get("access_token"))
        if "code" in request.args:
            # Verifies state parameter and obtain next url
            state_token = request.args.get("state")
            assert state_token
            # Checks authenticity and integrity of state and decodes the value.
            state = serializer.loads(state_token)
            # Verifies that state is for this session and that next parameter
            # has not been modified.
            assert state["sid"] == _create_identifier()
            # Stores next URL
            next_url = state["next"]
            gitlab_code = request.args.get("code")
            params = {
                "client_id": REANA_GITLAB_OAUTH_APP_ID,
                "client_secret": REANA_GITLAB_OAUTH_APP_SECRET,
                "redirect_uri": url_for(".gitlab_oauth", _external=True),
                "code": gitlab_code,
                "grant_type": "authorization_code",
            }
            gitlab_response = requests.post(REANA_GITLAB_URL + "/oauth/token",
                                            data=params).content
            secrets_store = REANAUserSecretsStore(str(user.id_))
            secrets_store.add_secrets(_format_gitlab_secrets(gitlab_response),
                                      overwrite=True)
            return redirect(next_url), 201
        else:
            return jsonify({"message": "OK"}), 200
    except ValueError:
        return jsonify({"message": "Token is not valid."}), 403
    except (AssertionError, BadData):
        return jsonify({"message": "State param is invalid."}), 403
    except Exception as e:
        logging.error(traceback.format_exc())
        return jsonify({"message": str(e)}), 500
Exemple #9
0
def gitlab_webhook():  # noqa
    r"""Endpoint to setup a GitLab webhook.
    ---
    post:
      summary: Set a webhook on a user project from GitLab
      operationId: create_gitlab_webhook
      description: >-
        Setup a webhook for a GitLab project on GitLab.
      produces:
       - application/json
      parameters:
      - name: project_id
        in: path
        description: The GitLab project id.
        required: true
        type: integer
      responses:
        201:
          description: >-
            The webhook was created.
        403:
          description: >-
            Request failed. User token not valid.
          examples:
            application/json:
              {
                "message": "Token is not valid."
              }
        500:
          description: >-
            Request failed. Internal controller error.
    delete:
      summary: Delete an existing webhook from GitLab
      operationId: delete_gitlab_webhook
      description: >-
        Remove an existing REANA webhook from a project on GitLab
      produces:
      - application/json
      parameters:
      - name: project_id
        in: path
        description: The GitLab project id.
        required: true
        type: integer
      - name: hook_id
        in: path
        description: The GitLab webhook id of the project.
        required: true
        type: integer
      responses:
        204:
          description: >-
            The webhook was properly deleted.
        404:
          description: >-
            No webhook found with provided id.
        403:
          description: >-
            Request failed. User token not valid.
          examples:
            application/json:
              {
                "message": "Token is not valid."
              }
        500:
          description: >-
            Request failed. Internal controller error.
    """

    try:
        if current_user.is_authenticated:
            user = _get_user_from_invenio_user(current_user.email)
        else:
            user = get_user_from_token(request.args.get("access_token"))
        secrets_store = REANAUserSecretsStore(str(user.id_))
        gitlab_token = secrets_store.get_secret_value("gitlab_access_token")
        parameters = request.json
        if request.method == "POST":
            gitlab_url = (REANA_GITLAB_URL + "/api/v4/projects/" +
                          "{0}/hooks?access_token={1}")
            webhook_payload = {
                "url": "https://{}/api/workflows".format(REANA_URL),
                "push_events": True,
                "push_events_branch_filter": "master",
                "merge_requests_events": True,
                "enable_ssl_verification": False,
                "token": user.access_token,
            }
            webhook = requests.post(
                gitlab_url.format(parameters["project_id"], gitlab_token),
                data=webhook_payload,
            )
            return jsonify({"id": webhook.json()["id"]}), 201
        elif request.method == "DELETE":
            gitlab_url = (REANA_GITLAB_URL + "/api/v4/projects/" +
                          "{0}/hooks/{1}?access_token={2}")
            resp = requests.delete(
                gitlab_url.format(parameters["project_id"],
                                  parameters["hook_id"], gitlab_token))
            return resp.content, resp.status_code

    except ValueError:
        return jsonify({"message": "Token is not valid."}), 403
    except Exception as e:
        logging.error(traceback.format_exc())
        return jsonify({"message": str(e)}), 500
Exemple #10
0
def request_token():
    r"""Endpoint to request user access token.

    ---
    put:
      summary: Requests a new access token for the authenticated user.
      description: >-
        This resource allows the user to create an empty REANA access token
        and mark it as requested.
      operationId: request_token
      produces:
        - application/json
      parameters:
        - name: access_token
          in: query
          description: API access_token of user.
          required: false
          type: string
      responses:
        200:
          description: >-
            User information correspoding to the session cookie sent
            in the request.
          schema:
            type: object
            properties:
              reana_token:
                type: object
                properties:
                  status:
                    type: string
                  requested_at:
                    type: string
          examples:
            application/json:
              {
                "reana_token": {
                  "status": "requested",
                  "requested_at": "Mon, 25 May 2020 10:45:15 GMT"
                }
              }
        401:
          description: >-
            Error message indicating that the uses is not authenticated.
          schema:
            type: object
            properties:
              error:
                type: string
          examples:
            application/json:
              {
                "error": "User not logged in"
              }
        403:
          description: >-
            Request failed. User token not valid.
          examples:
            application/json:
              {
                "message": "Token is not valid."
              }
        500:
          description: >-
            Request failed. Internal server error.
          examples:
            application/json:
              {
                "message": "Internal server error."
              }
    """
    try:
        user = None
        if current_user.is_authenticated:
            user = _get_user_from_invenio_user(current_user.email)
        elif "access_token" in request.args:
            user = get_user_from_token(request.args.get("access_token"))
        user.request_access_token()
        user.log_action(AuditLogAction.request_token)
        email_subject = f"[{REANA_URL}] Token request ({user.email})"
        fields = [
            "id_",
            "email",
            "full_name",
            "username",
            "access_token",
            "access_token_status",
        ]
        email_body = "New user access token request:\n\n" + "\n".join(
            [f"{f}: {getattr(user, f, None)}" for f in fields]
        )
        send_email(ADMIN_EMAIL, email_subject, email_body)
        return (
            jsonify(
                {
                    "reana_token": {
                        "status": user.access_token_status,
                        "requested_at": user.latest_access_token.created,
                    }
                }
            ),
            200,
        )

    except HTTPError as e:
        logging.error(traceback.format_exc())
        return jsonify(e.response.json()), e.response.status_code
    except ValueError as e:
        logging.error(traceback.format_exc())
        return jsonify({"message": str(e)}), 403
    except Exception as e:
        logging.error(traceback.format_exc())
        return jsonify({"message": str(e)}), 500
Exemple #11
0
def get_me():
    r"""Endpoint to get user information.

    ---
    get:
      summary: Gets information about authenticated user.
      description: >-
        This resource provides basic information about an authenticated
        user based on the session cookie presence.
      operationId: get_me
      produces:
        - application/json
      parameters:
        - name: access_token
          in: query
          description: API access_token of user.
          required: false
          type: string
      responses:
        200:
          description: >-
            User information correspoding to the session cookie sent
            in the request.
          schema:
            type: object
            properties:
              email:
                type: string
              reana_token:
                type: object
                properties:
                  value:
                    type: string
                  status:
                    type: string
                  requested_at:
                    type: string
          examples:
            application/json:
              {
                "email": "*****@*****.**",
                "reana_token": {
                    "value": "Drmhze6EPcv0fN_81Bj-nA",
                    "status": "active",
                    "requested_at": "Mon, 25 May 2020 10:39:57 GMT",
                },
                "full_name": "John Doe",
                "username": "******"
              }
        401:
          description: >-
            Error message indicating that the uses is not authenticated.
          schema:
            type: object
            properties:
              error:
                type: string
          examples:
            application/json:
              {
                "error": "User not logged in"
              }
        403:
          description: >-
            Request failed. User token not valid.
          examples:
            application/json:
              {
                "message": "Token is not valid."
              }
        500:
          description: >-
            Request failed. Internal server error.
          examples:
            application/json:
              {
                "message": "Internal server error."
              }
    """
    try:
        me = None
        if current_user.is_authenticated:
            me = _get_user_from_invenio_user(current_user.email)
        elif "access_token" in request.args:
            me = get_user_from_token(request.args.get("access_token"))
        if me:
            return (
                jsonify(
                    {
                        "email": me.email,
                        "reana_token": {
                            "value": me.access_token,
                            "status": me.access_token_status,
                            "requested_at": me.latest_access_token.created
                            if me.latest_access_token
                            else None,
                        },
                        "full_name": me.full_name,
                        "username": me.username,
                        "announcement": REANA_UI_ANNOUNCEMENT,
                    }
                ),
                200,
            )
        return jsonify(message="User not logged in"), 401
    except HTTPError as e:
        logging.error(traceback.format_exc())
        return jsonify(e.response.json()), e.response.status_code
    except ValueError as e:
        logging.error(traceback.format_exc())
        return jsonify({"message": str(e)}), 403
    except Exception as e:
        logging.error(traceback.format_exc())
        return jsonify({"message": str(e)}), 500
Exemple #12
0
def gitlab_oauth():  # noqa
    r"""Endpoint to authorize REANA on GitLab.
    ---
    get:
      summary: Get access token from GitLab
      operationId: gitlab_oauth
      description: >-
        Authorize REANA on GitLab.
      produces:
       - application/json
      responses:
        200:
          description: >-
            Ping succeeded.
          schema:
            type: object
            properties:
              message:
                type: string
              status:
                type: string
          examples:
            application/json:
              message: OK
              status: 200
        201:
          description: >-
            Authorization succeeded. GitLab secret created.
          schema:
            type: object
            properties:
              message:
                type: string
              status:
                type: string
          examples:
            application/json:
              message: GitLab secret created
              status: 201
        403:
          description: >-
            Request failed. User token not valid.
          examples:
            application/json:
              {
                "message": "Token is not valid."
              }
        500:
          description: >-
            Request failed. Internal controller error.
    """
    try:
        if current_user.is_authenticated:
            user = _get_user_from_invenio_user(current_user.email)
        else:
            user = get_user_from_token(request.args.get('access_token'))
        if 'code' in request.args:
            gitlab_code = request.args.get('code')
            parameters = "client_id={0}&" + \
                         "client_secret={1}&code={2}&" + \
                         "grant_type=authorization_code&redirect_uri={3}"
            parameters = parameters.format(REANA_GITLAB_OAUTH_APP_ID,
                                           REANA_GITLAB_OAUTH_APP_SECRET,
                                           gitlab_code,
                                           REANA_GITLAB_OAUTH_REDIRECT_URL)
            gitlab_response = requests.post(REANA_GITLAB_URL + '/oauth/token',
                                            data=parameters).content
            secrets_store = REANAUserSecretsStore(str(user.id_))
            secrets_store.add_secrets(_format_gitlab_secrets(gitlab_response),
                                      overwrite=True)
            return jsonify({"message": "GitLab secret created"}), 201
        else:
            return jsonify({"message": "OK"}), 200
    except ValueError:
        return jsonify({"message": "Token is not valid."}), 403
    except Exception as e:
        logging.error(traceback.format_exc())
        return jsonify({"message": str(e)}), 500