mix tasks consistency
[akkoma] / test / plugs / oauth_scopes_plug_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Plugs.OAuthScopesPlugTest do
6 use Pleroma.Web.ConnCase
7
8 alias Pleroma.Plugs.OAuthScopesPlug
9 alias Pleroma.Repo
10
11 import Mock
12 import Pleroma.Factory
13
14 test "is not performed if marked as skipped", %{conn: conn} do
15 with_mock OAuthScopesPlug, [:passthrough], perform: &passthrough([&1, &2]) do
16 conn =
17 conn
18 |> OAuthScopesPlug.skip_plug()
19 |> OAuthScopesPlug.call(%{scopes: ["random_scope"]})
20
21 refute called(OAuthScopesPlug.perform(:_, :_))
22 refute conn.halted
23 end
24 end
25
26 test "if `token.scopes` fulfills specified 'any of' conditions, " <>
27 "proceeds with no op",
28 %{conn: conn} do
29 token = insert(:oauth_token, scopes: ["read", "write"]) |> Repo.preload(:user)
30
31 conn =
32 conn
33 |> assign(:user, token.user)
34 |> assign(:token, token)
35 |> OAuthScopesPlug.call(%{scopes: ["read"]})
36
37 refute conn.halted
38 assert conn.assigns[:user]
39 end
40
41 test "if `token.scopes` fulfills specified 'all of' conditions, " <>
42 "proceeds with no op",
43 %{conn: conn} do
44 token = insert(:oauth_token, scopes: ["scope1", "scope2", "scope3"]) |> Repo.preload(:user)
45
46 conn =
47 conn
48 |> assign(:user, token.user)
49 |> assign(:token, token)
50 |> OAuthScopesPlug.call(%{scopes: ["scope2", "scope3"], op: :&})
51
52 refute conn.halted
53 assert conn.assigns[:user]
54 end
55
56 describe "with `fallback: :proceed_unauthenticated` option, " do
57 test "if `token.scopes` doesn't fulfill specified conditions, " <>
58 "clears :user and :token assigns",
59 %{conn: conn} do
60 user = insert(:user)
61 token1 = insert(:oauth_token, scopes: ["read", "write"], user: user)
62
63 for token <- [token1, nil], op <- [:|, :&] do
64 ret_conn =
65 conn
66 |> assign(:user, user)
67 |> assign(:token, token)
68 |> OAuthScopesPlug.call(%{
69 scopes: ["follow"],
70 op: op,
71 fallback: :proceed_unauthenticated
72 })
73
74 refute ret_conn.halted
75 refute ret_conn.assigns[:user]
76 refute ret_conn.assigns[:token]
77 end
78 end
79 end
80
81 describe "without :fallback option, " do
82 test "if `token.scopes` does not fulfill specified 'any of' conditions, " <>
83 "returns 403 and halts",
84 %{conn: conn} do
85 for token <- [insert(:oauth_token, scopes: ["read", "write"]), nil] do
86 any_of_scopes = ["follow", "push"]
87
88 ret_conn =
89 conn
90 |> assign(:token, token)
91 |> OAuthScopesPlug.call(%{scopes: any_of_scopes})
92
93 assert ret_conn.halted
94 assert 403 == ret_conn.status
95
96 expected_error = "Insufficient permissions: #{Enum.join(any_of_scopes, " | ")}."
97 assert Jason.encode!(%{error: expected_error}) == ret_conn.resp_body
98 end
99 end
100
101 test "if `token.scopes` does not fulfill specified 'all of' conditions, " <>
102 "returns 403 and halts",
103 %{conn: conn} do
104 for token <- [insert(:oauth_token, scopes: ["read", "write"]), nil] do
105 token_scopes = (token && token.scopes) || []
106 all_of_scopes = ["write", "follow"]
107
108 conn =
109 conn
110 |> assign(:token, token)
111 |> OAuthScopesPlug.call(%{scopes: all_of_scopes, op: :&})
112
113 assert conn.halted
114 assert 403 == conn.status
115
116 expected_error =
117 "Insufficient permissions: #{Enum.join(all_of_scopes -- token_scopes, " & ")}."
118
119 assert Jason.encode!(%{error: expected_error}) == conn.resp_body
120 end
121 end
122 end
123
124 describe "with hierarchical scopes, " do
125 test "if `token.scopes` fulfills specified 'any of' conditions, " <>
126 "proceeds with no op",
127 %{conn: conn} do
128 token = insert(:oauth_token, scopes: ["read", "write"]) |> Repo.preload(:user)
129
130 conn =
131 conn
132 |> assign(:user, token.user)
133 |> assign(:token, token)
134 |> OAuthScopesPlug.call(%{scopes: ["read:something"]})
135
136 refute conn.halted
137 assert conn.assigns[:user]
138 end
139
140 test "if `token.scopes` fulfills specified 'all of' conditions, " <>
141 "proceeds with no op",
142 %{conn: conn} do
143 token = insert(:oauth_token, scopes: ["scope1", "scope2", "scope3"]) |> Repo.preload(:user)
144
145 conn =
146 conn
147 |> assign(:user, token.user)
148 |> assign(:token, token)
149 |> OAuthScopesPlug.call(%{scopes: ["scope1:subscope", "scope2:subscope"], op: :&})
150
151 refute conn.halted
152 assert conn.assigns[:user]
153 end
154 end
155
156 describe "filter_descendants/2" do
157 test "filters scopes which directly match or are ancestors of supported scopes" do
158 f = fn scopes, supported_scopes ->
159 OAuthScopesPlug.filter_descendants(scopes, supported_scopes)
160 end
161
162 assert f.(["read", "follow"], ["write", "read"]) == ["read"]
163
164 assert f.(["read", "write:something", "follow"], ["write", "read"]) ==
165 ["read", "write:something"]
166
167 assert f.(["admin:read"], ["write", "read"]) == []
168
169 assert f.(["admin:read"], ["write", "admin"]) == ["admin:read"]
170 end
171 end
172
173 describe "transform_scopes/2" do
174 setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage])
175
176 setup do
177 {:ok, %{f: &OAuthScopesPlug.transform_scopes/2}}
178 end
179
180 test "with :admin option, prefixes all requested scopes with `admin:` " <>
181 "and [optionally] keeps only prefixed scopes, " <>
182 "depending on `[:auth, :enforce_oauth_admin_scope_usage]` setting",
183 %{f: f} do
184 Pleroma.Config.put([:auth, :enforce_oauth_admin_scope_usage], false)
185
186 assert f.(["read"], %{admin: true}) == ["admin:read", "read"]
187
188 assert f.(["read", "write"], %{admin: true}) == [
189 "admin:read",
190 "read",
191 "admin:write",
192 "write"
193 ]
194
195 Pleroma.Config.put([:auth, :enforce_oauth_admin_scope_usage], true)
196
197 assert f.(["read:accounts"], %{admin: true}) == ["admin:read:accounts"]
198
199 assert f.(["read", "write:reports"], %{admin: true}) == [
200 "admin:read",
201 "admin:write:reports"
202 ]
203 end
204
205 test "with no supported options, returns unmodified scopes", %{f: f} do
206 assert f.(["read"], %{}) == ["read"]
207 assert f.(["read", "write"], %{}) == ["read", "write"]
208 end
209 end
210 end