Commit aba93fe2 authored by Steve Abrams's avatar Steve Abrams Committed by Dmitriy Zaporozhets

OAuth2 support for GitLab personal access tokens

PATs are accepted using the OAuth2 compliant header
"Authorization: Bearer {token}" in order to allow for
OAuth requests while 2FA is enabled.
parent 30a0d460
...@@ -7,6 +7,7 @@ class PersonalAccessToken < ApplicationRecord ...@@ -7,6 +7,7 @@ class PersonalAccessToken < ApplicationRecord
add_authentication_token_field :token, digest: true add_authentication_token_field :token, digest: true
REDIS_EXPIRY_TIME = 3.minutes REDIS_EXPIRY_TIME = 3.minutes
TOKEN_LENGTH = 20
serialize :scopes, Array # rubocop:disable Cop/ActiveRecordSerialize serialize :scopes, Array # rubocop:disable Cop/ActiveRecordSerialize
......
---
title: Personal access tokens are accepted using OAuth2 header format
merge_request: 30277
author:
type: added
...@@ -272,6 +272,12 @@ Example of using the personal access token in a header: ...@@ -272,6 +272,12 @@ Example of using the personal access token in a header:
curl --header "Private-Token: <your_access_token>" https://gitlab.example.com/api/v4/projects curl --header "Private-Token: <your_access_token>" https://gitlab.example.com/api/v4/projects
``` ```
You can also use personal access tokens with OAuth-compliant headers:
```shell
curl --header "Authorization: Bearer <your_access_token>" https://gitlab.example.com/api/v4/projects
```
Read more about [personal access tokens][pat]. Read more about [personal access tokens][pat].
### Session cookie ### Session cookie
......
...@@ -49,35 +49,32 @@ Registry. ...@@ -49,35 +49,32 @@ Registry.
## Authenticating to the GitLab NPM Registry ## Authenticating to the GitLab NPM Registry
If a project is private or you want to upload an NPM package to GitLab, If a project is private or you want to upload an NPM package to GitLab,
credentials will need to be provided for authentication. Support is available credentials will need to be provided for authentication. Support is available for [OAuth tokens](../../../api/oauth2.md#resource-owner-password-credentials-flow) or [personal access tokens](../../profile/personal_access_tokens.md).
only for [OAuth tokens](../../../api/oauth2.md#resource-owner-password-credentials-flow).
CAUTION: **2FA not supported:** CAUTION: **2FA is only supported with personal access tokens:**
Authentication for personal access tokens is not yet supported If you have 2FA enabled, you need to use a [personal access token](../../profile/personal_access_tokens.md) with OAuth headers. Standard OAuth tokens won't be able to authenticate to the GitLab NPM Registry.
([#9140](https://gitlab.com/gitlab-org/gitlab-ee/issues/9140)). If you have 2FA
enabled, you won't be able to authenticate to the GitLab NPM Registry.
### Authenticating with an OAuth token ### Authenticating with an OAuth token
To authenticate with an [OAuth token](../../../api/oauth2.md#resource-owner-password-credentials-flow), To authenticate with an [OAuth token](../../../api/oauth2.md#resource-owner-password-credentials-flow)
add a corresponding section to your `.npmrc` file: or [personal access token](../../profile/personal_access_tokens.md), add a corresponding section to your `.npmrc` file:
```ini ```ini
; Set URL for your scoped packages. ; Set URL for your scoped packages.
; For example package with name `@foo/bar` will use this URL for download ; For example package with name `@foo/bar` will use this URL for download
@foo:registry=https://gitlab.com/api/v4/packages/npm/ @foo:registry=https://gitlab.com/api/v4/packages/npm/
; Add the OAuth token for the scoped packages URL. This will allow you to download ; Add the token for the scoped packages URL. This will allow you to download
; `@foo/` packages from private projects. ; `@foo/` packages from private projects.
//gitlab.com/api/v4/packages/npm/:_authToken=<your_oauth_token> //gitlab.com/api/v4/packages/npm/:_authToken=<your_token>
; Add OAuth token for uploading to the registry. Replace <your_project_id> ; Add token for uploading to the registry. Replace <your_project_id>
; with the project you want your package to be uploaded to. ; with the project you want your package to be uploaded to.
//gitlab.com/api/v4/projects/<your_project_id>/packages/npm/:_authToken=<your_oauth_token> //gitlab.com/api/v4/projects/<your_project_id>/packages/npm/:_authToken=<your_token>
``` ```
Replace `<your_project_id>` with your project ID which can be found on the home page Replace `<your_project_id>` with your project ID which can be found on the home page
of your project and `<your_oauth_token>` with your OAuth token. of your project and `<your_token>` with your OAuth or personal access token.
If you have a self-hosted GitLab installation, replace `gitlab.com` with your If you have a self-hosted GitLab installation, replace `gitlab.com` with your
domain name. domain name.
......
...@@ -90,8 +90,8 @@ module Gitlab ...@@ -90,8 +90,8 @@ module Gitlab
def find_personal_access_token def find_personal_access_token
token = token =
current_request.params[PRIVATE_TOKEN_PARAM].presence || current_request.params[PRIVATE_TOKEN_PARAM].presence ||
current_request.env[PRIVATE_TOKEN_HEADER].presence current_request.env[PRIVATE_TOKEN_HEADER].presence ||
parsed_oauth_token
return unless token return unless token
# Expiration, revocation and scopes are verified in `validate_access_token!` # Expiration, revocation and scopes are verified in `validate_access_token!`
...@@ -99,9 +99,12 @@ module Gitlab ...@@ -99,9 +99,12 @@ module Gitlab
end end
def find_oauth_access_token def find_oauth_access_token
token = Doorkeeper::OAuth::Token.from_request(current_request, *Doorkeeper.configuration.access_token_methods) token = parsed_oauth_token
return unless token return unless token
# PATs with OAuth headers are not handled by OauthAccessToken
return if matches_personal_access_token_length?(token)
# Expiration, revocation and scopes are verified in `validate_access_token!` # Expiration, revocation and scopes are verified in `validate_access_token!`
oauth_token = OauthAccessToken.by_token(token) oauth_token = OauthAccessToken.by_token(token)
raise UnauthorizedError unless oauth_token raise UnauthorizedError unless oauth_token
...@@ -110,6 +113,14 @@ module Gitlab ...@@ -110,6 +113,14 @@ module Gitlab
oauth_token oauth_token
end end
def parsed_oauth_token
Doorkeeper::OAuth::Token.from_request(current_request, *Doorkeeper.configuration.access_token_methods)
end
def matches_personal_access_token_length?(token)
token.length == PersonalAccessToken::TOKEN_LENGTH
end
# Check if the request is GET/HEAD, or if CSRF token is valid. # Check if the request is GET/HEAD, or if CSRF token is valid.
def verified_request? def verified_request?
Gitlab::RequestForgeryProtection.verified?(current_request.env) Gitlab::RequestForgeryProtection.verified?(current_request.env)
......
...@@ -138,6 +138,20 @@ describe Gitlab::Auth::UserAuthFinders do ...@@ -138,6 +138,20 @@ describe Gitlab::Auth::UserAuthFinders do
expect { find_user_from_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError) expect { find_user_from_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
end end
end end
context 'with OAuth headers' do
it 'returns user' do
env['HTTP_AUTHORIZATION'] = "Bearer #{personal_access_token.token}"
expect(find_user_from_access_token).to eq user
end
it 'returns exception if invalid personal_access_token' do
env['HTTP_AUTHORIZATION'] = 'Bearer invalid_20byte_token'
expect { find_personal_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
end
end end
describe '#find_user_from_web_access_token' do describe '#find_user_from_web_access_token' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment