Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Jérome Perrin
gitlab-ce
Commits
81246e56
Commit
81246e56
authored
Dec 27, 2016
by
Simon Vocella
Committed by
Tiago Botelho
Feb 28, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
manage personal_access_tokens through api
parent
4c4810b3
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
427 additions
and
3 deletions
+427
-3
app/models/personal_access_token.rb
app/models/personal_access_token.rb
+5
-0
db/migrate/20161228124936_change_expires_at_to_date_in_personal_access_tokens.rb
...36_change_expires_at_to_date_in_personal_access_tokens.rb
+18
-0
db/schema.rb
db/schema.rb
+1
-1
lib/api/api.rb
lib/api/api.rb
+1
-0
lib/api/entities.rb
lib/api/entities.rb
+12
-0
lib/api/personal_access_tokens.rb
lib/api/personal_access_tokens.rb
+56
-0
lib/api/users.rb
lib/api/users.rb
+64
-0
spec/controllers/profiles/personal_access_tokens_spec.rb
spec/controllers/profiles/personal_access_tokens_spec.rb
+2
-2
spec/factories/personal_access_tokens.rb
spec/factories/personal_access_tokens.rb
+8
-0
spec/models/personal_access_token_spec.rb
spec/models/personal_access_token_spec.rb
+18
-0
spec/requests/api/personal_access_tokens_spec.rb
spec/requests/api/personal_access_tokens_spec.rb
+107
-0
spec/requests/api/users_spec.rb
spec/requests/api/users_spec.rb
+135
-0
No files found.
app/models/personal_access_token.rb
View file @
81246e56
class
PersonalAccessToken
<
ActiveRecord
::
Base
include
Expirable
include
TokenAuthenticatable
add_authentication_token_field
:token
...
...
@@ -19,4 +20,8 @@ class PersonalAccessToken < ActiveRecord::Base
self
.
revoked
=
true
self
.
save
end
def
active?
!
revoked?
&&
!
expired?
end
end
db/migrate/20161228124936_change_expires_at_to_date_in_personal_access_tokens.rb
0 → 100644
View file @
81246e56
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class
ChangeExpiresAtToDateInPersonalAccessTokens
<
ActiveRecord
::
Migration
include
Gitlab
::
Database
::
MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME
=
true
DOWNTIME_REASON
=
'This migration requires downtime because it alters expires_at column from datetime to date'
def
up
change_column
:personal_access_tokens
,
:expires_at
,
:date
end
def
down
change_column
:personal_access_tokens
,
:expires_at
,
:datetime
end
end
db/schema.rb
View file @
81246e56
...
...
@@ -879,7 +879,7 @@ ActiveRecord::Schema.define(version: 20170216141440) do
t
.
string
"token"
,
null:
false
t
.
string
"name"
,
null:
false
t
.
boolean
"revoked"
,
default:
false
t
.
date
time
"expires_at"
t
.
date
"expires_at"
t
.
datetime
"created_at"
,
null:
false
t
.
datetime
"updated_at"
,
null:
false
t
.
string
"scopes"
,
default:
"--- []
\n
"
,
null:
false
...
...
lib/api/api.rb
View file @
81246e56
...
...
@@ -93,6 +93,7 @@ module API
mount
::
API
::
Namespaces
mount
::
API
::
Notes
mount
::
API
::
NotificationSettings
mount
::
API
::
PersonalAccessTokens
mount
::
API
::
Pipelines
mount
::
API
::
ProjectHooks
mount
::
API
::
Projects
...
...
lib/api/entities.rb
View file @
81246e56
...
...
@@ -696,5 +696,17 @@ module API
expose
:id
,
:message
,
:starts_at
,
:ends_at
,
:color
,
:font
expose
:active?
,
as: :active
end
class
BasicPersonalAccessToken
<
Grape
::
Entity
expose
:id
,
:name
,
:revoked
,
:created_at
,
:scopes
expose
:active?
,
as: :active
expose
:expires_at
do
|
personal_access_token
|
personal_access_token
.
expires_at
?
personal_access_token
.
expires_at
.
strftime
(
"%Y-%m-%d"
)
:
nil
end
end
class
PersonalAccessToken
<
BasicPersonalAccessToken
expose
:token
end
end
end
lib/api/personal_access_tokens.rb
0 → 100644
View file @
81246e56
module
API
class
PersonalAccessTokens
<
Grape
::
API
before
{
authenticate!
}
resource
:personal_access_tokens
do
desc
'Retrieve personal access tokens'
params
do
optional
:state
,
type:
String
,
default:
'all'
,
values:
%w[all active inactive]
,
desc:
'Filters (all|active|inactive) personal_access_tokens'
end
get
do
personal_access_tokens
=
current_user
.
personal_access_tokens
case
params
[
:state
]
when
"active"
personal_access_tokens
=
personal_access_tokens
.
active
when
"inactive"
personal_access_tokens
=
personal_access_tokens
.
inactive
end
present
personal_access_tokens
,
with:
Entities
::
BasicPersonalAccessToken
end
desc
'Create a personal access token'
params
do
requires
:name
,
type:
String
,
desc:
'The name of the personal access token'
optional
:expires_at
,
type:
Date
,
desc:
'The expiration date in the format YEAR-MONTH-DAY of the personal access token'
optional
:scopes
,
type:
Array
,
desc:
'The array of scopes of the personal access token'
end
post
do
parameters
=
declared_params
(
include_missing:
false
)
parameters
[
:user_id
]
=
current_user
.
id
personal_access_token
=
PersonalAccessToken
.
generate
(
parameters
)
if
personal_access_token
.
save
present
personal_access_token
,
with:
Entities
::
PersonalAccessToken
else
render_validation_error!
(
personal_access_token
)
end
end
desc
'Revoke a personal access token'
params
do
requires
:personal_access_token_id
,
type:
Integer
,
desc:
'The ID of the personal access token'
end
delete
':personal_access_token_id'
do
personal_access_token
=
PersonalAccessToken
.
find_by
(
id:
params
[
:personal_access_token_id
],
user_id:
current_user
.
id
)
not_found!
(
'PersonalAccessToken'
)
unless
personal_access_token
personal_access_token
.
revoke!
present
personal_access_token
,
with:
Entities
::
BasicPersonalAccessToken
end
end
end
end
lib/api/users.rb
View file @
81246e56
...
...
@@ -362,6 +362,70 @@ module API
present
paginate
(
events
),
with:
Entities
::
Event
end
desc
'Retrieve personal access tokens. Available only for admins.'
params
do
requires
:user_id
,
type:
Integer
optional
:state
,
type:
String
,
default:
'all'
,
values:
%w[all active inactive]
,
desc:
'Filters (all|active|inactive) personal_access_tokens'
end
get
':user_id/personal_access_tokens'
do
authenticated_as_admin!
user
=
User
.
find_by
(
id:
params
[
:user_id
])
not_found!
(
'User'
)
unless
user
personal_access_tokens
=
user
.
personal_access_tokens
case
params
[
:state
]
when
"active"
personal_access_tokens
=
personal_access_tokens
.
active
when
"inactive"
personal_access_tokens
=
personal_access_tokens
.
inactive
end
present
personal_access_tokens
,
with:
Entities
::
PersonalAccessToken
end
desc
'Create a personal access token. Available only for admins.'
params
do
requires
:user_id
,
type:
Integer
,
desc:
'The ID of the user'
requires
:name
,
type:
String
,
desc:
'The name of the personal access token'
optional
:expires_at
,
type:
Date
,
desc:
'The expiration date in the format YEAR-MONTH-DAY of the personal access token'
optional
:scopes
,
type:
Array
,
desc:
'The array of scopes of the personal access token'
end
post
':user_id/personal_access_tokens'
do
authenticated_as_admin!
user
=
User
.
find_by
(
id:
params
[
:user_id
])
not_found!
(
'User'
)
unless
user
personal_access_token
=
PersonalAccessToken
.
generate
(
declared_params
(
include_missing:
false
))
if
personal_access_token
.
save
present
personal_access_token
,
with:
Entities
::
PersonalAccessToken
else
render_validation_error!
(
personal_access_token
)
end
end
desc
'Revoke a personal access token. Available only for admins.'
params
do
requires
:user_id
,
type:
Integer
,
desc:
'The ID of the user'
requires
:personal_access_token_id
,
type:
Integer
,
desc:
'The ID of the personal access token'
end
delete
':user_id/personal_access_tokens/:personal_access_token_id'
do
authenticated_as_admin!
user
=
User
.
find_by
(
id:
params
[
:user_id
])
not_found!
(
'User'
)
unless
user
personal_access_token
=
PersonalAccessToken
.
find_by
(
id:
params
[
:personal_access_token_id
])
not_found!
(
'PersonalAccessToken'
)
unless
personal_access_token
personal_access_token
.
revoke!
present
personal_access_token
,
with:
Entities
::
PersonalAccessToken
end
end
resource
:user
do
...
...
spec/controllers/profiles/personal_access_tokens_spec.rb
View file @
81246e56
...
...
@@ -22,12 +22,12 @@ describe Profiles::PersonalAccessTokensController do
end
it
"allows creation of a token with an expiry date"
do
expires_at
=
5
.
days
.
from_now
expires_at
=
5
.
days
.
from_now
.
to_date
post
:create
,
personal_access_token:
{
name:
FFaker
::
Product
.
brand
,
expires_at:
expires_at
}
expect
(
created_token
).
not_to
be_nil
expect
(
created_token
.
expires_at
.
to_i
).
to
eq
(
expires_at
.
to_i
)
expect
(
created_token
.
expires_at
).
to
eq
(
expires_at
)
end
context
"scopes"
do
...
...
spec/factories/personal_access_tokens.rb
View file @
81246e56
...
...
@@ -6,5 +6,13 @@ FactoryGirl.define do
revoked
false
expires_at
{
5
.
days
.
from_now
}
scopes
[
'api'
]
factory
:revoked_personal_access_token
do
revoked
true
end
factory
:expired_personal_access_token
do
expires_at
{
1
.
day
.
ago
}
end
end
end
spec/models/personal_access_token_spec.rb
View file @
81246e56
...
...
@@ -12,4 +12,22 @@ describe PersonalAccessToken, models: true do
expect
(
personal_access_token
).
not_to
be_persisted
end
end
describe
".active?"
do
let
(
:active_personal_access_token
)
{
build
(
:personal_access_token
)
}
let
(
:revoked_personal_access_token
)
{
build
(
:revoked_personal_access_token
)
}
let
(
:expired_personal_access_token
)
{
build
(
:expired_personal_access_token
)
}
it
"returns false if the personal_access_token is revoked"
do
expect
(
revoked_personal_access_token
).
not_to
be_active
end
it
"returns false if the personal_access_token is expired"
do
expect
(
expired_personal_access_token
).
not_to
be_active
end
it
"returns true if the personal_access_token is not revoked and not expired"
do
expect
(
active_personal_access_token
).
to
be_active
end
end
end
spec/requests/api/personal_access_tokens_spec.rb
0 → 100644
View file @
81246e56
require
'spec_helper'
describe
API
::
PersonalAccessTokens
,
api:
true
do
include
ApiHelpers
let
(
:user
)
{
create
(
:user
)
}
describe
"GET /personal_access_tokens"
do
let!
(
:active_personal_access_token
)
{
create
(
:personal_access_token
,
user:
user
)
}
let!
(
:revoked_personal_access_token
)
{
create
(
:revoked_personal_access_token
,
user:
user
)
}
let!
(
:expired_personal_access_token
)
{
create
(
:expired_personal_access_token
,
user:
user
)
}
it
'returns an array of personal access tokens without exposing the token'
do
get
api
(
"/personal_access_tokens"
,
user
)
expect
(
response
).
to
have_http_status
(
200
)
expect
(
json_response
).
to
be_an
Array
expect
(
json_response
.
size
).
to
eq
(
3
)
json_personal_access_token
=
json_response
.
detect
do
|
personal_access_token
|
personal_access_token
[
'id'
]
==
active_personal_access_token
.
id
end
expect
(
json_personal_access_token
[
'name'
]).
to
eq
(
active_personal_access_token
.
name
)
expect
(
json_personal_access_token
[
'token'
]).
not_to
be_present
end
it
'returns an array of active personal access tokens if active is set to true'
do
get
api
(
"/personal_access_tokens?state=active"
,
user
)
expect
(
response
).
to
have_http_status
(
200
)
expect
(
json_response
).
to
be_an
Array
expect
(
json_response
).
to
all
(
include
(
'active'
=>
true
))
end
it
'returns an array of inactive personal access tokens if active is set to false'
do
get
api
(
"/personal_access_tokens?state=inactive"
,
user
)
expect
(
response
).
to
have_http_status
(
200
)
expect
(
json_response
).
to
be_an
Array
expect
(
json_response
).
to
all
(
include
(
'active'
=>
false
))
end
end
describe
'POST /personal_access_tokens'
do
let
(
:name
)
{
'my new pat'
}
let
(
:expires_at
)
{
'2016-12-28'
}
let
(
:scopes
)
{
[
'api'
,
'read_user'
]
}
it
'returns validation error if personal access token miss some attributes'
do
post
api
(
"/personal_access_tokens"
,
user
)
expect
(
response
).
to
have_http_status
(
400
)
expect
(
json_response
[
'error'
]).
to
eq
(
'name is missing'
)
end
it
'creates a personal access token'
do
post
api
(
"/personal_access_tokens"
,
user
),
name:
name
,
expires_at:
expires_at
,
scopes:
scopes
expect
(
response
).
to
have_http_status
(
201
)
personal_access_token_id
=
json_response
[
'id'
]
expect
(
json_response
[
'name'
]).
to
eq
(
name
)
expect
(
json_response
[
'scopes'
]).
to
eq
(
scopes
)
expect
(
json_response
[
'expires_at'
]).
to
eq
(
expires_at
)
expect
(
json_response
[
'id'
]).
to
be_present
expect
(
json_response
[
'created_at'
]).
to
be_present
expect
(
json_response
[
'active'
]).
to
eq
(
false
)
expect
(
json_response
[
'revoked'
]).
to
eq
(
false
)
expect
(
json_response
[
'token'
]).
to
be_present
expect
(
PersonalAccessToken
.
find
(
personal_access_token_id
)).
not_to
eq
(
nil
)
end
end
describe
'DELETE /personal_access_tokens/:personal_access_token_id'
do
let!
(
:personal_access_token
)
{
create
(
:personal_access_token
,
user:
user
,
revoked:
false
)
}
let!
(
:personal_access_token_of_another_user
)
{
create
(
:personal_access_token
,
revoked:
false
)
}
it
'returns a 404 error if personal access token not found'
do
delete
api
(
"/personal_access_tokens/42"
,
user
)
expect
(
response
).
to
have_http_status
(
404
)
expect
(
json_response
[
'message'
]).
to
eq
(
'404 PersonalAccessToken Not Found'
)
end
it
'returns a 404 error if personal access token exists but it is a personal access tokens of another user'
do
delete
api
(
"/personal_access_tokens/
#{
personal_access_token_of_another_user
.
id
}
"
,
user
)
expect
(
response
).
to
have_http_status
(
404
)
expect
(
json_response
[
'message'
]).
to
eq
(
'404 PersonalAccessToken Not Found'
)
end
it
'revokes a personal access token and does not expose token in the json response'
do
delete
api
(
"/personal_access_tokens/
#{
personal_access_token
.
id
}
"
,
user
)
expect
(
response
).
to
have_http_status
(
200
)
expect
(
personal_access_token
.
revoked
).
to
eq
(
false
)
expect
(
personal_access_token
.
reload
.
revoked
).
to
eq
(
true
)
expect
(
json_response
[
'revoked'
]).
to
eq
(
true
)
expect
(
json_response
[
'token'
]).
not_to
be_present
end
end
end
spec/requests/api/users_spec.rb
View file @
81246e56
...
...
@@ -10,6 +10,7 @@ describe API::Users, api: true do
let
(
:omniauth_user
)
{
create
(
:omniauth_user
)
}
let
(
:ldap_user
)
{
create
(
:omniauth_user
,
provider:
'ldapmain'
)
}
let
(
:ldap_blocked_user
)
{
create
(
:omniauth_user
,
provider:
'ldapmain'
,
state:
'ldap_blocked'
)
}
let
(
:not_existing_user_id
)
{
(
User
.
maximum
(
'id'
)
||
0
)
+
10
}
describe
"GET /users"
do
context
"when unauthenticated"
do
...
...
@@ -1155,4 +1156,138 @@ describe API::Users, api: true do
expect
(
json_response
[
'message'
]).
to
eq
(
'404 User Not Found'
)
end
end
describe
'GET /users/:user_id/personal_access_tokens'
do
let!
(
:active_personal_access_token
)
{
create
(
:personal_access_token
,
user:
user
)
}
let!
(
:revoked_personal_access_token
)
{
create
(
:revoked_personal_access_token
,
user:
user
)
}
let!
(
:expired_personal_access_token
)
{
create
(
:expired_personal_access_token
,
user:
user
)
}
it
'returns a 404 error if user not found'
do
get
api
(
"/users/
#{
not_existing_user_id
}
/personal_access_tokens"
,
admin
)
expect
(
response
).
to
have_http_status
(
404
)
expect
(
json_response
[
'message'
]).
to
eq
(
'404 User Not Found'
)
end
it
'returns a 403 error when authenticated as normal user'
do
get
api
(
"/users/
#{
not_existing_user_id
}
/personal_access_tokens"
,
user
)
expect
(
response
).
to
have_http_status
(
403
)
expect
(
json_response
[
'message'
]).
to
eq
(
'403 Forbidden'
)
end
it
'returns an array of personal access tokens'
do
get
api
(
"/users/
#{
user
.
id
}
/personal_access_tokens"
,
admin
)
expect
(
response
).
to
have_http_status
(
200
)
expect
(
json_response
).
to
be_an
Array
expect
(
json_response
.
size
).
to
eq
(
3
)
expect
(
json_response
.
detect
do
|
personal_access_token
|
personal_access_token
[
'id'
]
==
active_personal_access_token
.
id
end
[
'token'
]).
to
eq
(
active_personal_access_token
.
token
)
end
it
'returns an array of active personal access tokens if active is set to true'
do
get
api
(
"/users/
#{
user
.
id
}
/personal_access_tokens?state=active"
,
admin
)
expect
(
response
).
to
have_http_status
(
200
)
expect
(
json_response
).
to
be_an
Array
expect
(
json_response
).
to
all
(
include
(
'active'
=>
true
))
end
it
'returns an array of inactive personal access tokens if active is set to false'
do
get
api
(
"/users/
#{
user
.
id
}
/personal_access_tokens?state=inactive"
,
admin
)
expect
(
response
).
to
have_http_status
(
200
)
expect
(
json_response
).
to
be_an
Array
expect
(
json_response
).
to
all
(
include
(
'active'
=>
false
))
end
end
describe
'POST /users/:user_id/personal_access_tokens'
do
let
(
:name
)
{
'my new pat'
}
let
(
:expires_at
)
{
'2016-12-28'
}
let
(
:scopes
)
{
[
'api'
,
'read_user'
]
}
it
'returns validation error if personal access token miss some attributes'
do
post
api
(
"/users/
#{
user
.
id
}
/personal_access_tokens"
,
admin
)
expect
(
response
).
to
have_http_status
(
400
)
expect
(
json_response
[
'error'
]).
to
eq
(
'name is missing'
)
end
it
'returns a 404 error if user not found'
do
post
api
(
"/users/
#{
not_existing_user_id
}
/personal_access_tokens"
,
admin
),
name:
name
,
expires_at:
expires_at
expect
(
response
).
to
have_http_status
(
404
)
expect
(
json_response
[
'message'
]).
to
eq
(
'404 User Not Found'
)
end
it
'returns a 403 error when authenticated as normal user'
do
post
api
(
"/users/
#{
user
.
id
}
/personal_access_tokens"
,
user
),
name:
name
,
expires_at:
expires_at
expect
(
response
).
to
have_http_status
(
403
)
expect
(
json_response
[
'message'
]).
to
eq
(
'403 Forbidden'
)
end
it
'creates a personal access token'
do
post
api
(
"/users/
#{
user
.
id
}
/personal_access_tokens"
,
admin
),
name:
name
,
expires_at:
expires_at
,
scopes:
scopes
expect
(
response
).
to
have_http_status
(
201
)
personal_access_token_id
=
json_response
[
'id'
]
expect
(
json_response
[
'name'
]).
to
eq
(
name
)
expect
(
json_response
[
'scopes'
]).
to
eq
(
scopes
)
expect
(
json_response
[
'expires_at'
]).
to
eq
(
expires_at
)
expect
(
json_response
[
'id'
]).
to
be_present
expect
(
json_response
[
'created_at'
]).
to
be_present
expect
(
json_response
[
'active'
]).
to
eq
(
false
)
expect
(
json_response
[
'revoked'
]).
to
eq
(
false
)
expect
(
json_response
[
'token'
]).
to
be_present
expect
(
PersonalAccessToken
.
find
(
personal_access_token_id
)).
not_to
eq
(
nil
)
end
end
describe
'DELETE /users/:id/personal_access_tokens/:personal_access_token_id'
do
let!
(
:personal_access_token
)
{
create
(
:personal_access_token
,
user:
user
,
revoked:
false
)
}
it
'returns a 404 error if user not found'
do
delete
api
(
"/users/
#{
not_existing_user_id
}
/personal_access_tokens/1"
,
admin
)
expect
(
response
).
to
have_http_status
(
404
)
expect
(
json_response
[
'message'
]).
to
eq
(
'404 User Not Found'
)
end
it
'returns a 404 error if personal access token not found'
do
delete
api
(
"/users/
#{
user
.
id
}
/personal_access_tokens/42"
,
admin
)
expect
(
response
).
to
have_http_status
(
404
)
expect
(
json_response
[
'message'
]).
to
eq
(
'404 PersonalAccessToken Not Found'
)
end
it
'returns a 403 error when authenticated as normal user'
do
delete
api
(
"/users/
#{
user
.
id
}
/personal_access_tokens/
#{
personal_access_token
.
id
}
"
,
user
)
expect
(
response
).
to
have_http_status
(
403
)
expect
(
json_response
[
'message'
]).
to
eq
(
'403 Forbidden'
)
end
it
'revokes a personal access token'
do
delete
api
(
"/users/
#{
user
.
id
}
/personal_access_tokens/
#{
personal_access_token
.
id
}
"
,
admin
)
expect
(
response
).
to
have_http_status
(
200
)
expect
(
personal_access_token
.
revoked
).
to
eq
(
false
)
expect
(
personal_access_token
.
reload
.
revoked
).
to
eq
(
true
)
expect
(
json_response
[
'revoked'
]).
to
eq
(
true
)
expect
(
json_response
[
'token'
]).
to
be_present
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment