05d0973d8b34adc7f6126f3ab75a93a82f780716
[anni] / test / pleroma / web / plugs / o_auth_scopes_plug_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.Plugs.OAuthScopesPlugTest do
6   use Pleroma.Web.ConnCase
7
8   alias Pleroma.Repo
9   alias Pleroma.Web.Plugs.OAuthScopesPlug
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 end