[#1427] Graceful clearance of OAuth admin scopes for non-admin users (no error raised).

PleromaFE and other clients may safely request admin scope(s): if user isn't an admin, request is successful but only non-admin scopes from request are granted.
This commit is contained in:
Ivan Tashkinov 2019-12-12 16:00:06 +03:00
parent 79532a7f7c
commit 81b05340e9
2 changed files with 57 additions and 44 deletions

View File

@ -79,7 +79,9 @@ defp authorize_admin_scopes(scopes, app_scopes, %User{} = user) do
if user.is_admin || !contains_admin_scopes?(scopes) || !contains_admin_scopes?(app_scopes) do if user.is_admin || !contains_admin_scopes?(scopes) || !contains_admin_scopes?(app_scopes) do
{:ok, scopes} {:ok, scopes}
else else
{:error, :unsupported_scopes} # Gracefully dropping admin scopes from requested scopes if user isn't an admin (not raising)
scopes = scopes -- OAuthScopesPlug.filter_descendants(scopes, ["admin"])
validate(scopes, app_scopes, user)
end end
end end

View File

@ -567,33 +567,41 @@ test "with existing authentication and OOB `redirect_uri`, redirects to app with
end end
describe "POST /oauth/authorize" do describe "POST /oauth/authorize" do
test "redirects with oauth authorization" do test "redirects with oauth authorization, " <>
user = insert(:user) "keeping only non-admin scopes for non-admin user" do
app = insert(:oauth_app, scopes: ["read", "write", "follow"]) app = insert(:oauth_app, scopes: ["read", "write", "admin"])
redirect_uri = OAuthController.default_redirect_uri(app) redirect_uri = OAuthController.default_redirect_uri(app)
conn = non_admin = insert(:user, is_admin: false)
build_conn() admin = insert(:user, is_admin: true)
|> post("/oauth/authorize", %{
"authorization" => %{
"name" => user.nickname,
"password" => "test",
"client_id" => app.client_id,
"redirect_uri" => redirect_uri,
"scope" => "read:subscope write",
"state" => "statepassed"
}
})
target = redirected_to(conn) for {user, expected_scopes} <- %{
assert target =~ redirect_uri non_admin => ["read:subscope", "write"],
admin => ["read:subscope", "write", "admin"]
} do
conn =
build_conn()
|> post("/oauth/authorize", %{
"authorization" => %{
"name" => user.nickname,
"password" => "test",
"client_id" => app.client_id,
"redirect_uri" => redirect_uri,
"scope" => "read:subscope write admin",
"state" => "statepassed"
}
})
query = URI.parse(target).query |> URI.query_decoder() |> Map.new() target = redirected_to(conn)
assert target =~ redirect_uri
assert %{"state" => "statepassed", "code" => code} = query query = URI.parse(target).query |> URI.query_decoder() |> Map.new()
auth = Repo.get_by(Authorization, token: code)
assert auth assert %{"state" => "statepassed", "code" => code} = query
assert auth.scopes == ["read:subscope", "write"] auth = Repo.get_by(Authorization, token: code)
assert auth
assert auth.scopes == expected_scopes
end
end end
test "returns 401 for wrong credentials", %{conn: conn} do test "returns 401 for wrong credentials", %{conn: conn} do
@ -623,31 +631,34 @@ test "returns 401 for wrong credentials", %{conn: conn} do
assert result =~ "Invalid Username/Password" assert result =~ "Invalid Username/Password"
end end
test "returns 401 for missing scopes", %{conn: conn} do test "returns 401 for missing scopes " <>
user = insert(:user) "(including all admin-only scopes for non-admin user)" do
app = insert(:oauth_app) user = insert(:user, is_admin: false)
app = insert(:oauth_app, scopes: ["read", "write", "admin"])
redirect_uri = OAuthController.default_redirect_uri(app) redirect_uri = OAuthController.default_redirect_uri(app)
result = for scope_param <- ["", "admin:read admin:write"] do
conn result =
|> post("/oauth/authorize", %{ build_conn()
"authorization" => %{ |> post("/oauth/authorize", %{
"name" => user.nickname, "authorization" => %{
"password" => "test", "name" => user.nickname,
"client_id" => app.client_id, "password" => "test",
"redirect_uri" => redirect_uri, "client_id" => app.client_id,
"state" => "statepassed", "redirect_uri" => redirect_uri,
"scope" => "" "state" => "statepassed",
} "scope" => scope_param
}) }
|> html_response(:unauthorized) })
|> html_response(:unauthorized)
# Keep the details # Keep the details
assert result =~ app.client_id assert result =~ app.client_id
assert result =~ redirect_uri assert result =~ redirect_uri
# Error message # Error message
assert result =~ "This action is outside the authorized scopes" assert result =~ "This action is outside the authorized scopes"
end
end end
test "returns 401 for scopes beyond app scopes hierarchy", %{conn: conn} do test "returns 401 for scopes beyond app scopes hierarchy", %{conn: conn} do