[#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,11 +567,18 @@ 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)
non_admin = insert(:user, is_admin: false)
admin = insert(:user, is_admin: true)
for {user, expected_scopes} <- %{
non_admin => ["read:subscope", "write"],
admin => ["read:subscope", "write", "admin"]
} do
conn = conn =
build_conn() build_conn()
|> post("/oauth/authorize", %{ |> post("/oauth/authorize", %{
@ -580,7 +587,7 @@ test "redirects with oauth authorization" do
"password" => "test", "password" => "test",
"client_id" => app.client_id, "client_id" => app.client_id,
"redirect_uri" => redirect_uri, "redirect_uri" => redirect_uri,
"scope" => "read:subscope write", "scope" => "read:subscope write admin",
"state" => "statepassed" "state" => "statepassed"
} }
}) })
@ -593,7 +600,8 @@ test "redirects with oauth authorization" do
assert %{"state" => "statepassed", "code" => code} = query assert %{"state" => "statepassed", "code" => code} = query
auth = Repo.get_by(Authorization, token: code) auth = Repo.get_by(Authorization, token: code)
assert auth assert auth
assert auth.scopes == ["read:subscope", "write"] 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,13 +631,15 @@ 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)
for scope_param <- ["", "admin:read admin:write"] do
result = result =
conn build_conn()
|> post("/oauth/authorize", %{ |> post("/oauth/authorize", %{
"authorization" => %{ "authorization" => %{
"name" => user.nickname, "name" => user.nickname,
@ -637,7 +647,7 @@ test "returns 401 for missing scopes", %{conn: conn} do
"client_id" => app.client_id, "client_id" => app.client_id,
"redirect_uri" => redirect_uri, "redirect_uri" => redirect_uri,
"state" => "statepassed", "state" => "statepassed",
"scope" => "" "scope" => scope_param
} }
}) })
|> html_response(:unauthorized) |> html_response(:unauthorized)
@ -649,6 +659,7 @@ test "returns 401 for missing scopes", %{conn: conn} do
# 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
user = insert(:user) user = insert(:user)