Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Pleroma
pleroma
Commits
091baf93
Commit
091baf93
authored
Apr 02, 2019
by
lain
Browse files
Merge branch 'features/mastoapi/2.6.0-force-login-option' into 'develop'
MastoAPI 2.6.0 `force_login` option Closes #734 See merge request
pleroma/pleroma!999
parents
26d509cc
15ce7104
Pipeline
#9864
passed with stages
in 4 minutes and 43 seconds
Changes
6
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
lib/pleroma/web/controller_helper.ex
View file @
091baf93
...
...
@@ -5,6 +5,11 @@
defmodule
Pleroma
.
Web
.
ControllerHelper
do
use
Pleroma
.
Web
,
:controller
# As in MastoAPI, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
@falsy_param_values
[
false
,
0
,
"0"
,
"f"
,
"F"
,
"false"
,
"FALSE"
,
"off"
,
"OFF"
]
def
truthy_param?
(
blank_value
)
when
blank_value
in
[
nil
,
""
],
do
:
nil
def
truthy_param?
(
value
),
do
:
value
not
in
@falsy_param_values
def
oauth_scopes
(
params
,
default
)
do
# Note: `scopes` is used by Mastodon — supporting it but sticking to
# OAuth's standard `scope` wherever we control it
...
...
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
View file @
091baf93
...
...
@@ -1249,16 +1249,22 @@ defp get_user_flavour(_) do
"glitch"
end
def
login
(
conn
,
%{
"code"
=>
code
})
do
def
login
(%{
assigns:
%{
user:
%
User
{}}}
=
conn
,
_params
)
do
redirect
(
conn
,
to:
local_mastodon_root_path
(
conn
))
end
@doc
"Local Mastodon FE login init action"
def
login
(
conn
,
%{
"code"
=>
auth_token
})
do
with
{
:ok
,
app
}
<-
get_or_make_app
(),
%
Authorization
{}
=
auth
<-
Repo
.
get_by
(
Authorization
,
token:
code
,
app_id:
app
.
id
),
%
Authorization
{}
=
auth
<-
Repo
.
get_by
(
Authorization
,
token:
auth_token
,
app_id:
app
.
id
),
{
:ok
,
token
}
<-
Token
.
exchange_token
(
app
,
auth
)
do
conn
|>
put_session
(
:oauth_token
,
token
.
token
)
|>
redirect
(
to:
"/web/getting-started"
)
|>
redirect
(
to:
local_mastodon_root_path
(
conn
)
)
end
end
@doc
"Local Mastodon FE callback action"
def
login
(
conn
,
_
)
do
with
{
:ok
,
app
}
<-
get_or_make_app
()
do
path
=
...
...
@@ -1276,6 +1282,8 @@ def login(conn, _) do
end
end
defp
local_mastodon_root_path
(
conn
),
do
:
mastodon_api_path
(
conn
,
:index
,
[
"getting-started"
])
defp
get_or_make_app
do
find_attrs
=
%{
client_name:
@local_mastodon_name
,
redirect_uris:
"."
}
scopes
=
[
"read"
,
"write"
,
"follow"
,
"push"
]
...
...
lib/pleroma/web/oauth/oauth_controller.ex
View file @
091baf93
...
...
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
alias
Pleroma
.
Repo
alias
Pleroma
.
User
alias
Pleroma
.
Web
.
Auth
.
Authenticator
alias
Pleroma
.
Web
.
ControllerHelper
alias
Pleroma
.
Web
.
OAuth
.
App
alias
Pleroma
.
Web
.
OAuth
.
Authorization
alias
Pleroma
.
Web
.
OAuth
.
Token
...
...
@@ -19,7 +20,28 @@ defmodule Pleroma.Web.OAuth.OAuthController do
action_fallback
(
Pleroma
.
Web
.
OAuth
.
FallbackController
)
def
authorize
(
conn
,
params
)
do
def
authorize
(%{
assigns:
%{
token:
%
Token
{}
=
token
}}
=
conn
,
params
)
do
if
ControllerHelper
.
truthy_param?
(
params
[
"force_login"
])
do
do_authorize
(
conn
,
params
)
else
redirect_uri
=
if
is_binary
(
params
[
"redirect_uri"
])
do
params
[
"redirect_uri"
]
else
app
=
Repo
.
preload
(
token
,
:app
)
.
app
app
.
redirect_uris
|>
String
.
split
()
|>
Enum
.
at
(
0
)
end
redirect
(
conn
,
external:
redirect_uri
(
conn
,
redirect_uri
))
end
end
def
authorize
(
conn
,
params
),
do
:
do_authorize
(
conn
,
params
)
defp
do_authorize
(
conn
,
params
)
do
app
=
Repo
.
get_by
(
App
,
client_id:
params
[
"client_id"
])
available_scopes
=
(
app
&&
app
.
scopes
)
||
[]
scopes
=
oauth_scopes
(
params
,
nil
)
||
available_scopes
...
...
@@ -51,13 +73,7 @@ def create_authorization(conn, %{
{
:missing_scopes
,
false
}
<-
{
:missing_scopes
,
scopes
==
[]},
{
:auth_active
,
true
}
<-
{
:auth_active
,
User
.
auth_active?
(
user
)},
{
:ok
,
auth
}
<-
Authorization
.
create_authorization
(
app
,
user
,
scopes
)
do
redirect_uri
=
if
redirect_uri
==
"."
do
# Special case: Local MastodonFE
mastodon_api_url
(
conn
,
:login
)
else
redirect_uri
end
redirect_uri
=
redirect_uri
(
conn
,
redirect_uri
)
cond
do
redirect_uri
==
"urn:ietf:wg:oauth:2.0:oob"
->
...
...
@@ -221,4 +237,9 @@ defp get_app_from_request(conn, params) do
nil
end
end
# Special case: Local MastodonFE
defp
redirect_uri
(
conn
,
"."
),
do
:
mastodon_api_url
(
conn
,
:login
)
defp
redirect_uri
(
_conn
,
redirect_uri
),
do
:
redirect_uri
end
lib/pleroma/web/router.ex
View file @
091baf93
...
...
@@ -5,6 +5,11 @@
defmodule
Pleroma
.
Web
.
Router
do
use
Pleroma
.
Web
,
:router
pipeline
:oauth
do
plug
(
:fetch_session
)
plug
(
Pleroma
.
Plugs
.
OAuthPlug
)
end
pipeline
:api
do
plug
(
:accepts
,
[
"json"
])
plug
(
:fetch_session
)
...
...
@@ -105,10 +110,6 @@ defmodule Pleroma.Web.Router do
plug
(
:accepts
,
[
"json"
,
"xml"
])
end
pipeline
:oauth
do
plug
(
:accepts
,
[
"html"
,
"json"
])
end
pipeline
:pleroma_api
do
plug
(
:accepts
,
[
"html"
,
"json"
])
end
...
...
@@ -200,7 +201,11 @@ defmodule Pleroma.Web.Router do
end
scope
"/oauth"
,
Pleroma
.
Web
.
OAuth
do
get
(
"/authorize"
,
OAuthController
,
:authorize
)
scope
[]
do
pipe_through
(
:oauth
)
get
(
"/authorize"
,
OAuthController
,
:authorize
)
end
post
(
"/authorize"
,
OAuthController
,
:create_authorization
)
post
(
"/token"
,
OAuthController
,
:token_exchange
)
post
(
"/revoke"
,
OAuthController
,
:token_revoke
)
...
...
test/support/factory.ex
View file @
091baf93
...
...
@@ -216,7 +216,7 @@ def oauth_app_factory do
redirect_uris:
"https://example.com/callback"
,
scopes:
[
"read"
,
"write"
,
"follow"
,
"push"
],
website:
"https://example.com"
,
client_id:
"aaabbb=="
,
client_id:
Ecto
.
UUID
.
generate
()
,
client_secret:
"aaa;/&bbb"
}
end
...
...
test/web/oauth/oauth_controller_test.exs
View file @
091baf93
...
...
@@ -10,261 +10,339 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
alias
Pleroma
.
Web
.
OAuth
.
Authorization
alias
Pleroma
.
Web
.
OAuth
.
Token
t
es
t
"redirects with
oauth
authoriz
ation
"
do
u
se
r
=
insert
(
:user
)
app
=
insert
(
:oauth_app
,
scopes:
[
"read"
,
"write"
,
"follow"
])
conn
=
build_conn
()
|>
post
(
"/oauth/authorize"
,
%{
"authorization"
=>
%{
"name"
=>
user
.
nickname
,
"password"
=>
"test"
,
"client_id"
=>
app
.
client_id
,
"redirect_uri"
=>
app
.
redirect_uris
,
"scope"
=>
"read write"
,
"state"
=>
"statepassed"
}
})
d
es
cribe
"GET /
oauth
/
authoriz
e
"
do
se
tup
do
session_opts
=
[
store:
:cookie
,
key:
"_test"
,
signing_salt:
"cooldude"
]
[
app:
insert
(
:oauth_app
,
redirect_uris:
"https://redirect.url"
)
,
conn:
build_conn
()
|>
Plug
.
Session
.
call
(
Plug
.
Session
.
init
(
session_opts
))
|>
fetch_session
()
]
end
target
=
redirected_to
(
conn
)
assert
target
=~
app
.
redirect_uris
test
"renders authentication page"
,
%{
app:
app
,
conn:
conn
}
do
conn
=
get
(
conn
,
"/oauth/authorize"
,
%{
"response_type"
=>
"code"
,
"client_id"
=>
app
.
client_id
,
"redirect_uri"
=>
app
.
redirect_uris
,
"scope"
=>
"read"
}
)
assert
html_response
(
conn
,
200
)
=~
~s(type="submit")
end
query
=
URI
.
parse
(
target
)
.
query
|>
URI
.
query_decoder
()
|>
Map
.
new
()
test
"renders authentication page if user is already authenticated but `force_login` is tru-ish"
,
%{
app:
app
,
conn:
conn
}
do
token
=
insert
(
:oauth_token
,
app_id:
app
.
id
)
conn
=
conn
|>
put_session
(
:oauth_token
,
token
.
token
)
|>
get
(
"/oauth/authorize"
,
%{
"response_type"
=>
"code"
,
"client_id"
=>
app
.
client_id
,
"redirect_uri"
=>
app
.
redirect_uris
,
"scope"
=>
"read"
,
"force_login"
=>
"true"
}
)
assert
html_response
(
conn
,
200
)
=~
~s(type="submit")
end
assert
%{
"state"
=>
"statepassed"
,
"code"
=>
code
}
=
query
auth
=
Repo
.
get_by
(
Authorization
,
token:
code
)
assert
auth
assert
auth
.
scopes
==
[
"read"
,
"write"
]
test
"redirects to app if user is already authenticated"
,
%{
app:
app
,
conn:
conn
}
do
token
=
insert
(
:oauth_token
,
app_id:
app
.
id
)
conn
=
conn
|>
put_session
(
:oauth_token
,
token
.
token
)
|>
get
(
"/oauth/authorize"
,
%{
"response_type"
=>
"code"
,
"client_id"
=>
app
.
client_id
,
"redirect_uri"
=>
app
.
redirect_uris
,
"scope"
=>
"read"
}
)
assert
redirected_to
(
conn
)
==
"https://redirect.url"
end
end
test
"returns 401 for wrong credentials"
,
%{
conn:
conn
}
do
user
=
insert
(
:user
)
app
=
insert
(
:oauth_app
)
describe
"POST /oauth/authorize"
do
test
"redirects with oauth authorization"
do
user
=
insert
(
:user
)
app
=
insert
(
:oauth_app
,
scopes:
[
"read"
,
"write"
,
"follow"
])
conn
=
build_conn
()
|>
post
(
"/oauth/authorize"
,
%{
"authorization"
=>
%{
"name"
=>
user
.
nickname
,
"password"
=>
"test"
,
"client_id"
=>
app
.
client_id
,
"redirect_uri"
=>
app
.
redirect_uris
,
"scope"
=>
"read write"
,
"state"
=>
"statepassed"
}
})
target
=
redirected_to
(
conn
)
assert
target
=~
app
.
redirect_uris
query
=
URI
.
parse
(
target
)
.
query
|>
URI
.
query_decoder
()
|>
Map
.
new
()
assert
%{
"state"
=>
"statepassed"
,
"code"
=>
code
}
=
query
auth
=
Repo
.
get_by
(
Authorization
,
token:
code
)
assert
auth
assert
auth
.
scopes
==
[
"read"
,
"write"
]
end
result
=
conn
|>
post
(
"/oauth/authorize"
,
%{
"authorization"
=>
%{
"name"
=>
user
.
nickname
,
"password"
=>
"wrong"
,
"client_id"
=>
app
.
client_id
,
"redirect_uri"
=>
app
.
redirect_uris
,
"state"
=>
"statepassed"
,
"scope"
=>
Enum
.
join
(
app
.
scopes
,
" "
)
}
})
|>
html_response
(
:unauthorized
)
# Keep the details
assert
result
=~
app
.
client_id
assert
result
=~
app
.
redirect_uris
# Error message
assert
result
=~
"Invalid Username/Password"
end
test
"returns 401 for wrong credentials"
,
%{
conn:
conn
}
do
user
=
insert
(
:user
)
app
=
insert
(
:oauth_app
)
result
=
conn
|>
post
(
"/oauth/authorize"
,
%{
"authorization"
=>
%{
"name"
=>
user
.
nickname
,
"password"
=>
"wrong"
,
"client_id"
=>
app
.
client_id
,
"redirect_uri"
=>
app
.
redirect_uris
,
"state"
=>
"statepassed"
,
"scope"
=>
Enum
.
join
(
app
.
scopes
,
" "
)
}
})
|>
html_response
(
:unauthorized
)
# Keep the details
assert
result
=~
app
.
client_id
assert
result
=~
app
.
redirect_uris
# Error message
assert
result
=~
"Invalid Username/Password"
end
test
"returns 401 for missing scopes"
,
%{
conn:
conn
}
do
user
=
insert
(
:user
)
app
=
insert
(
:oauth_app
)
test
"returns 401 for missing scopes"
,
%{
conn:
conn
}
do
user
=
insert
(
:user
)
app
=
insert
(
:oauth_app
)
result
=
conn
|>
post
(
"/oauth/authorize"
,
%{
"authorization"
=>
%{
"name"
=>
user
.
nickname
,
"password"
=>
"test"
,
"client_id"
=>
app
.
client_id
,
"redirect_uri"
=>
app
.
redirect_uris
,
"state"
=>
"statepassed"
,
"scope"
=>
""
}
})
|>
html_response
(
:unauthorized
)
# Keep the details
assert
result
=~
app
.
client_id
assert
result
=~
app
.
redirect_uris
# Error message
assert
result
=~
"This action is outside the authorized scopes"
end
result
=
conn
|>
post
(
"/oauth/authorize"
,
%{
"authorization"
=>
%{
"name"
=>
user
.
nickname
,
"password"
=>
"test"
,
"client_id"
=>
app
.
client_id
,
"redirect_uri"
=>
app
.
redirect_uris
,
"state"
=>
"statepassed"
,
"scope"
=>
""
}
})
|>
html_response
(
:unauthorized
)
# Keep the details
assert
result
=~
app
.
client_id
assert
result
=~
app
.
redirect_uris
# Error message
assert
result
=~
"This action is outside the authorized scopes"
test
"returns 401 for scopes beyond app scopes"
,
%{
conn:
conn
}
do
user
=
insert
(
:user
)
app
=
insert
(
:oauth_app
,
scopes:
[
"read"
,
"write"
])
result
=
conn
|>
post
(
"/oauth/authorize"
,
%{
"authorization"
=>
%{
"name"
=>
user
.
nickname
,
"password"
=>
"test"
,
"client_id"
=>
app
.
client_id
,
"redirect_uri"
=>
app
.
redirect_uris
,
"state"
=>
"statepassed"
,
"scope"
=>
"read write follow"
}
})
|>
html_response
(
:unauthorized
)
# Keep the details
assert
result
=~
app
.
client_id
assert
result
=~
app
.
redirect_uris
# Error message
assert
result
=~
"This action is outside the authorized scopes"
end
end
test
"returns 401 for scopes beyond app scopes"
,
%{
conn:
conn
}
do
user
=
insert
(
:user
)
app
=
insert
(
:oauth_app
,
scopes:
[
"read"
,
"write"
])
describe
"POST /oauth/token"
do
test
"issues a token for an all-body request"
do
user
=
insert
(
:user
)
app
=
insert
(
:oauth_app
,
scopes:
[
"read"
,
"write"
])
result
=
conn
|>
post
(
"/oauth/authorize"
,
%{
"authorization"
=>
%{
"name"
=>
user
.
nickname
,
"
password"
=>
"test
"
,
"c
lient_id"
=>
app
.
client_id
,
{
:ok
,
auth
}
=
Authorization
.
create_authorization
(
app
,
user
,
[
"write"
])
conn
=
build_conn
()
|>
post
(
"/oauth/token"
,
%{
"
grant_type"
=>
"authorization_code
"
,
"c
ode"
=>
auth
.
token
,
"redirect_uri"
=>
app
.
redirect_uris
,
"state"
=>
"statepassed"
,
"scope"
=>
"read write follow"
}
})
|>
html_response
(
:unauthorized
)
# Keep the details
assert
result
=~
app
.
client_id
assert
result
=~
app
.
redirect_uris
# Error message
assert
result
=~
"This action is outside the authorized scopes"
end
"client_id"
=>
app
.
client_id
,
"client_secret"
=>
app
.
client_secret
})
test
"issues a token for an all-body request"
do
user
=
insert
(
:user
)
app
=
insert
(
:oauth_app
,
scopes:
[
"read"
,
"write"
])
assert
%{
"access_token"
=>
token
,
"me"
=>
ap_id
}
=
json_response
(
conn
,
200
)
{
:ok
,
auth
}
=
Authorization
.
create_authorization
(
app
,
user
,
[
"write"
])
token
=
Repo
.
get_by
(
Token
,
token:
token
)
assert
token
assert
token
.
scopes
==
auth
.
scopes
assert
user
.
ap_id
==
ap_id
end
conn
=
build_conn
()
|>
post
(
"/oauth/token"
,
%{
"grant_type"
=>
"authorization_code"
,
"code"
=>
auth
.
token
,
"redirect_uri"
=>
app
.
redirect_uris
,
"client_id"
=>
app
.
client_id
,
"client_secret"
=>
app
.
client_secret
})
test
"issues a token for `password` grant_type with valid credentials, with full permissions by default"
do
password
=
"testpassword"
user
=
insert
(
:user
,
password_hash:
Comeonin
.
Pbkdf2
.
hashpwsalt
(
password
))
assert
%{
"access_token"
=>
token
,
"me"
=>
ap_id
}
=
json_response
(
conn
,
200
)
app
=
insert
(
:oauth_app
,
scopes:
[
"read"
,
"write"
]
)
token
=
Repo
.
get_by
(
Token
,
token:
token
)
assert
token
assert
token
.
scopes
==
auth
.
scopes
assert
user
.
ap_id
==
ap_id
end
# Note: "scope" param is intentionally omitted
conn
=
build_conn
()
|>
post
(
"/oauth/token"
,
%{
"grant_type"
=>
"password"
,
"username"
=>
user
.
nickname
,
"password"
=>
password
,
"client_id"
=>
app
.
client_id
,
"client_secret"
=>
app
.
client_secret
})
test
"issues a token for `password` grant_type with valid credentials, with full permissions by default"
do
password
=
"testpassword"
user
=
insert
(
:user
,
password_hash:
Comeonin
.
Pbkdf2
.
hashpwsalt
(
password
))
assert
%{
"access_token"
=>
token
}
=
json_response
(
conn
,
200
)
app
=
insert
(
:oauth_app
,
scopes:
[
"read"
,
"write"
])
token
=
Repo
.
get_by
(
Token
,
token:
token
)
assert
token
assert
token
.
scopes
==
app
.
scopes
end
# Note: "scope" param is intentionally omitted
conn
=
build_conn
()
|>
post
(
"/oauth/token"
,
%{
"grant_type"
=>
"password"
,
"username"
=>
user
.
nickname
,
"password"
=>
password
,
"client_id"
=>
app
.
client_id
,
"client_secret"
=>
app
.
client_secret
})
test
"issues a token for request with HTTP basic auth client credentials"
do
user
=
insert
(
:user
)
app
=
insert
(
:oauth_app
,
scopes:
[
"scope1"
,
"scope2"
,
"scope3"
])
assert
%{
"access_token"
=>
token
}
=
json_response
(
conn
,
200
)
{
:ok
,
auth
}
=
Authorization
.
create_authorization
(
app
,
user
,
[
"scope1"
,
"scope2"
])
assert
auth
.
scopes
==
[
"scope1"
,
"scope2"
]
token
=
Repo
.
get_by
(
Token
,
token:
token
)
assert
token
assert
token
.
scopes
==
app
.
scopes
end
app_encoded
=
(
URI
.
encode_www_form
(
app
.
client_id
)
<>
":"
<>
URI
.
encode_www_form
(
app
.
client_secret
))
|>
Base
.
encode64
()
test
"issues a token for request with HTTP basic auth client credentials"
do
user
=
insert
(
:user
)
app
=
insert
(
:oauth_app
,
scopes:
[
"scope1"
,
"scope2"
,
"scope3"
])
conn
=
build_conn
()
|>
put_req_header
(
"authorization"
,
"Basic "
<>
app_encoded
)
|>
post
(
"/oauth/token"
,
%{
"grant_type"
=>
"authorization_code"
,
"code"
=>
auth
.
token
,
"redirect_uri"
=>
app
.
redirect_uris
})
{
:ok
,
auth
}
=
Authorization
.
create_authorization
(
app
,
user
,
[
"scope1"
,
"scope2"
])
assert
auth
.
scopes
==
[
"scope1"
,
"scope2"
]
assert
%{
"access_token"
=>
token
,
"scope"
=>
scope
}
=
json_response
(
conn
,
200
)
app_encoded
=
(
URI
.
encode_www_form
(
app
.
client_id
)
<>
":"
<>
URI
.
encode_www_form
(
app
.
client_secret
))
|>
Base
.
encode64
()
assert
scope
==
"scope1 scope2"
conn
=
build_conn
()
|>
put_req_header
(
"authorization"
,
"Basic "
<>
app_encoded
)
|>
post
(
"/oauth/token"
,
%{
"grant_type"
=>
"authorization_code"
,
"code"
=>
auth
.
token
,
"redirect_uri"
=>
app
.
redirect_uris
})
token
=
Repo
.
get_by
(
Token
,
token:
token
)
assert
token
assert
token
.
scopes
==
[
"scope1"
,
"scope2"
]
end
assert
%{
"access_token"
=>
token
,
"scope"
=>
scope
}
=
json_response
(
conn
,
200
)
test
"rejects token exchange with invalid client credentials"
do
user
=
insert
(
:user
)
app
=
insert
(
:oauth_app
)
assert
scope
==
"scope1 scope2"
{
:ok
,
auth
}
=
Authorization
.
create_authorization
(
app
,
user
)
token
=
Repo
.
get_by
(
Token
,
token:
token
)
assert
token
assert
token
.
scopes
==
[
"scope1"
,
"scope2"
]
end
conn
=
build_conn
()
|>
put_req_header
(
"authorization"
,
"Basic JTIxOiVGMCU5RiVBNCVCNwo="
)
|>
post
(
"/oauth/token"
,
%{
"grant_type"
=>
"authorization_code"
,
"code"
=>
auth
.
token
,
"redirect_uri"
=>
app
.
redirect_uris
})
test
"rejects token exchange with invalid client credentials"
do
user
=
insert
(
:user
)
app
=
insert
(
:oauth_app
)
assert
resp
=
json_response
(
conn
,
400
)