Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop
[akkoma] / test / web / oauth / oauth_controller_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.OAuth.OAuthControllerTest do
6 use Pleroma.Web.ConnCase
7 import Pleroma.Factory
8
9 alias Pleroma.Repo
10 alias Pleroma.Web.OAuth.Authorization
11 alias Pleroma.Web.OAuth.OAuthController
12 alias Pleroma.Web.OAuth.Token
13
14 @session_opts [
15 store: :cookie,
16 key: "_test",
17 signing_salt: "cooldude"
18 ]
19 clear_config_all([:instance, :account_activation_required])
20
21 describe "in OAuth consumer mode, " do
22 setup do
23 [
24 app: insert(:oauth_app),
25 conn:
26 build_conn()
27 |> Plug.Session.call(Plug.Session.init(@session_opts))
28 |> fetch_session()
29 ]
30 end
31
32 clear_config([:auth, :oauth_consumer_strategies]) do
33 Pleroma.Config.put(
34 [:auth, :oauth_consumer_strategies],
35 ~w(twitter facebook)
36 )
37 end
38
39 test "GET /oauth/authorize renders auth forms, including OAuth consumer form", %{
40 app: app,
41 conn: conn
42 } do
43 conn =
44 get(
45 conn,
46 "/oauth/authorize",
47 %{
48 "response_type" => "code",
49 "client_id" => app.client_id,
50 "redirect_uri" => OAuthController.default_redirect_uri(app),
51 "scope" => "read"
52 }
53 )
54
55 assert response = html_response(conn, 200)
56 assert response =~ "Sign in with Twitter"
57 assert response =~ o_auth_path(conn, :prepare_request)
58 end
59
60 test "GET /oauth/prepare_request encodes parameters as `state` and redirects", %{
61 app: app,
62 conn: conn
63 } do
64 conn =
65 get(
66 conn,
67 "/oauth/prepare_request",
68 %{
69 "provider" => "twitter",
70 "authorization" => %{
71 "scope" => "read follow",
72 "client_id" => app.client_id,
73 "redirect_uri" => OAuthController.default_redirect_uri(app),
74 "state" => "a_state"
75 }
76 }
77 )
78
79 assert response = html_response(conn, 302)
80
81 redirect_query = URI.parse(redirected_to(conn)).query
82 assert %{"state" => state_param} = URI.decode_query(redirect_query)
83 assert {:ok, state_components} = Poison.decode(state_param)
84
85 expected_client_id = app.client_id
86 expected_redirect_uri = app.redirect_uris
87
88 assert %{
89 "scope" => "read follow",
90 "client_id" => ^expected_client_id,
91 "redirect_uri" => ^expected_redirect_uri,
92 "state" => "a_state"
93 } = state_components
94 end
95
96 test "with user-bound registration, GET /oauth/<provider>/callback redirects to `redirect_uri` with `code`",
97 %{app: app, conn: conn} do
98 registration = insert(:registration)
99 redirect_uri = OAuthController.default_redirect_uri(app)
100
101 state_params = %{
102 "scope" => Enum.join(app.scopes, " "),
103 "client_id" => app.client_id,
104 "redirect_uri" => redirect_uri,
105 "state" => ""
106 }
107
108 conn =
109 conn
110 |> assign(:ueberauth_auth, %{provider: registration.provider, uid: registration.uid})
111 |> get(
112 "/oauth/twitter/callback",
113 %{
114 "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
115 "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
116 "provider" => "twitter",
117 "state" => Poison.encode!(state_params)
118 }
119 )
120
121 assert response = html_response(conn, 302)
122 assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
123 end
124
125 test "with user-unbound registration, GET /oauth/<provider>/callback renders registration_details page",
126 %{app: app, conn: conn} do
127 user = insert(:user)
128
129 state_params = %{
130 "scope" => "read write",
131 "client_id" => app.client_id,
132 "redirect_uri" => OAuthController.default_redirect_uri(app),
133 "state" => "a_state"
134 }
135
136 conn =
137 conn
138 |> assign(:ueberauth_auth, %{
139 provider: "twitter",
140 uid: "171799000",
141 info: %{nickname: user.nickname, email: user.email, name: user.name, description: nil}
142 })
143 |> get(
144 "/oauth/twitter/callback",
145 %{
146 "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
147 "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
148 "provider" => "twitter",
149 "state" => Poison.encode!(state_params)
150 }
151 )
152
153 assert response = html_response(conn, 200)
154 assert response =~ ~r/name="op" type="submit" value="register"/
155 assert response =~ ~r/name="op" type="submit" value="connect"/
156 assert response =~ user.email
157 assert response =~ user.nickname
158 end
159
160 test "on authentication error, GET /oauth/<provider>/callback redirects to `redirect_uri`", %{
161 app: app,
162 conn: conn
163 } do
164 state_params = %{
165 "scope" => Enum.join(app.scopes, " "),
166 "client_id" => app.client_id,
167 "redirect_uri" => OAuthController.default_redirect_uri(app),
168 "state" => ""
169 }
170
171 conn =
172 conn
173 |> assign(:ueberauth_failure, %{errors: [%{message: "(error description)"}]})
174 |> get(
175 "/oauth/twitter/callback",
176 %{
177 "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
178 "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
179 "provider" => "twitter",
180 "state" => Poison.encode!(state_params)
181 }
182 )
183
184 assert response = html_response(conn, 302)
185 assert redirected_to(conn) == app.redirect_uris
186 assert get_flash(conn, :error) == "Failed to authenticate: (error description)."
187 end
188
189 test "GET /oauth/registration_details renders registration details form", %{
190 app: app,
191 conn: conn
192 } do
193 conn =
194 get(
195 conn,
196 "/oauth/registration_details",
197 %{
198 "authorization" => %{
199 "scopes" => app.scopes,
200 "client_id" => app.client_id,
201 "redirect_uri" => OAuthController.default_redirect_uri(app),
202 "state" => "a_state",
203 "nickname" => nil,
204 "email" => "john@doe.com"
205 }
206 }
207 )
208
209 assert response = html_response(conn, 200)
210 assert response =~ ~r/name="op" type="submit" value="register"/
211 assert response =~ ~r/name="op" type="submit" value="connect"/
212 end
213
214 test "with valid params, POST /oauth/register?op=register redirects to `redirect_uri` with `code`",
215 %{
216 app: app,
217 conn: conn
218 } do
219 registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
220 redirect_uri = OAuthController.default_redirect_uri(app)
221
222 conn =
223 conn
224 |> put_session(:registration_id, registration.id)
225 |> post(
226 "/oauth/register",
227 %{
228 "op" => "register",
229 "authorization" => %{
230 "scopes" => app.scopes,
231 "client_id" => app.client_id,
232 "redirect_uri" => redirect_uri,
233 "state" => "a_state",
234 "nickname" => "availablenick",
235 "email" => "available@email.com"
236 }
237 }
238 )
239
240 assert response = html_response(conn, 302)
241 assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
242 end
243
244 test "with unlisted `redirect_uri`, POST /oauth/register?op=register results in HTTP 401",
245 %{
246 app: app,
247 conn: conn
248 } do
249 registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
250 unlisted_redirect_uri = "http://cross-site-request.com"
251
252 conn =
253 conn
254 |> put_session(:registration_id, registration.id)
255 |> post(
256 "/oauth/register",
257 %{
258 "op" => "register",
259 "authorization" => %{
260 "scopes" => app.scopes,
261 "client_id" => app.client_id,
262 "redirect_uri" => unlisted_redirect_uri,
263 "state" => "a_state",
264 "nickname" => "availablenick",
265 "email" => "available@email.com"
266 }
267 }
268 )
269
270 assert response = html_response(conn, 401)
271 end
272
273 test "with invalid params, POST /oauth/register?op=register renders registration_details page",
274 %{
275 app: app,
276 conn: conn
277 } do
278 another_user = insert(:user)
279 registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
280
281 params = %{
282 "op" => "register",
283 "authorization" => %{
284 "scopes" => app.scopes,
285 "client_id" => app.client_id,
286 "redirect_uri" => OAuthController.default_redirect_uri(app),
287 "state" => "a_state",
288 "nickname" => "availablenickname",
289 "email" => "available@email.com"
290 }
291 }
292
293 for {bad_param, bad_param_value} <-
294 [{"nickname", another_user.nickname}, {"email", another_user.email}] do
295 bad_registration_attrs = %{
296 "authorization" => Map.put(params["authorization"], bad_param, bad_param_value)
297 }
298
299 bad_params = Map.merge(params, bad_registration_attrs)
300
301 conn =
302 conn
303 |> put_session(:registration_id, registration.id)
304 |> post("/oauth/register", bad_params)
305
306 assert html_response(conn, 403) =~ ~r/name="op" type="submit" value="register"/
307 assert get_flash(conn, :error) == "Error: #{bad_param} has already been taken."
308 end
309 end
310
311 test "with valid params, POST /oauth/register?op=connect redirects to `redirect_uri` with `code`",
312 %{
313 app: app,
314 conn: conn
315 } do
316 user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("testpassword"))
317 registration = insert(:registration, user: nil)
318 redirect_uri = OAuthController.default_redirect_uri(app)
319
320 conn =
321 conn
322 |> put_session(:registration_id, registration.id)
323 |> post(
324 "/oauth/register",
325 %{
326 "op" => "connect",
327 "authorization" => %{
328 "scopes" => app.scopes,
329 "client_id" => app.client_id,
330 "redirect_uri" => redirect_uri,
331 "state" => "a_state",
332 "name" => user.nickname,
333 "password" => "testpassword"
334 }
335 }
336 )
337
338 assert response = html_response(conn, 302)
339 assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
340 end
341
342 test "with unlisted `redirect_uri`, POST /oauth/register?op=connect results in HTTP 401`",
343 %{
344 app: app,
345 conn: conn
346 } do
347 user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("testpassword"))
348 registration = insert(:registration, user: nil)
349 unlisted_redirect_uri = "http://cross-site-request.com"
350
351 conn =
352 conn
353 |> put_session(:registration_id, registration.id)
354 |> post(
355 "/oauth/register",
356 %{
357 "op" => "connect",
358 "authorization" => %{
359 "scopes" => app.scopes,
360 "client_id" => app.client_id,
361 "redirect_uri" => unlisted_redirect_uri,
362 "state" => "a_state",
363 "name" => user.nickname,
364 "password" => "testpassword"
365 }
366 }
367 )
368
369 assert response = html_response(conn, 401)
370 end
371
372 test "with invalid params, POST /oauth/register?op=connect renders registration_details page",
373 %{
374 app: app,
375 conn: conn
376 } do
377 user = insert(:user)
378 registration = insert(:registration, user: nil)
379
380 params = %{
381 "op" => "connect",
382 "authorization" => %{
383 "scopes" => app.scopes,
384 "client_id" => app.client_id,
385 "redirect_uri" => OAuthController.default_redirect_uri(app),
386 "state" => "a_state",
387 "name" => user.nickname,
388 "password" => "wrong password"
389 }
390 }
391
392 conn =
393 conn
394 |> put_session(:registration_id, registration.id)
395 |> post("/oauth/register", params)
396
397 assert html_response(conn, 401) =~ ~r/name="op" type="submit" value="connect"/
398 assert get_flash(conn, :error) == "Invalid Username/Password"
399 end
400 end
401
402 describe "GET /oauth/authorize" do
403 setup do
404 [
405 app: insert(:oauth_app, redirect_uris: "https://redirect.url"),
406 conn:
407 build_conn()
408 |> Plug.Session.call(Plug.Session.init(@session_opts))
409 |> fetch_session()
410 ]
411 end
412
413 test "renders authentication page", %{app: app, conn: conn} do
414 conn =
415 get(
416 conn,
417 "/oauth/authorize",
418 %{
419 "response_type" => "code",
420 "client_id" => app.client_id,
421 "redirect_uri" => OAuthController.default_redirect_uri(app),
422 "scope" => "read"
423 }
424 )
425
426 assert html_response(conn, 200) =~ ~s(type="submit")
427 end
428
429 test "properly handles internal calls with `authorization`-wrapped params", %{
430 app: app,
431 conn: conn
432 } do
433 conn =
434 get(
435 conn,
436 "/oauth/authorize",
437 %{
438 "authorization" => %{
439 "response_type" => "code",
440 "client_id" => app.client_id,
441 "redirect_uri" => OAuthController.default_redirect_uri(app),
442 "scope" => "read"
443 }
444 }
445 )
446
447 assert html_response(conn, 200) =~ ~s(type="submit")
448 end
449
450 test "renders authentication page if user is already authenticated but `force_login` is tru-ish",
451 %{app: app, conn: conn} do
452 token = insert(:oauth_token, app_id: app.id)
453
454 conn =
455 conn
456 |> put_session(:oauth_token, token.token)
457 |> get(
458 "/oauth/authorize",
459 %{
460 "response_type" => "code",
461 "client_id" => app.client_id,
462 "redirect_uri" => OAuthController.default_redirect_uri(app),
463 "scope" => "read",
464 "force_login" => "true"
465 }
466 )
467
468 assert html_response(conn, 200) =~ ~s(type="submit")
469 end
470
471 test "with existing authentication and non-OOB `redirect_uri`, redirects to app with `token` and `state` params",
472 %{
473 app: app,
474 conn: conn
475 } do
476 token = insert(:oauth_token, app_id: app.id)
477
478 conn =
479 conn
480 |> put_session(:oauth_token, token.token)
481 |> get(
482 "/oauth/authorize",
483 %{
484 "response_type" => "code",
485 "client_id" => app.client_id,
486 "redirect_uri" => OAuthController.default_redirect_uri(app),
487 "state" => "specific_client_state",
488 "scope" => "read"
489 }
490 )
491
492 assert URI.decode(redirected_to(conn)) ==
493 "https://redirect.url?access_token=#{token.token}&state=specific_client_state"
494 end
495
496 test "with existing authentication and unlisted non-OOB `redirect_uri`, redirects without credentials",
497 %{
498 app: app,
499 conn: conn
500 } do
501 unlisted_redirect_uri = "http://cross-site-request.com"
502 token = insert(:oauth_token, app_id: app.id)
503
504 conn =
505 conn
506 |> put_session(:oauth_token, token.token)
507 |> get(
508 "/oauth/authorize",
509 %{
510 "response_type" => "code",
511 "client_id" => app.client_id,
512 "redirect_uri" => unlisted_redirect_uri,
513 "state" => "specific_client_state",
514 "scope" => "read"
515 }
516 )
517
518 assert redirected_to(conn) == unlisted_redirect_uri
519 end
520
521 test "with existing authentication and OOB `redirect_uri`, redirects to app with `token` and `state` params",
522 %{
523 app: app,
524 conn: conn
525 } do
526 token = insert(:oauth_token, app_id: app.id)
527
528 conn =
529 conn
530 |> put_session(:oauth_token, token.token)
531 |> get(
532 "/oauth/authorize",
533 %{
534 "response_type" => "code",
535 "client_id" => app.client_id,
536 "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
537 "scope" => "read"
538 }
539 )
540
541 assert html_response(conn, 200) =~ "Authorization exists"
542 end
543 end
544
545 describe "POST /oauth/authorize" do
546 test "redirects with oauth authorization" do
547 user = insert(:user)
548 app = insert(:oauth_app, scopes: ["read", "write", "follow"])
549 redirect_uri = OAuthController.default_redirect_uri(app)
550
551 conn =
552 build_conn()
553 |> post("/oauth/authorize", %{
554 "authorization" => %{
555 "name" => user.nickname,
556 "password" => "test",
557 "client_id" => app.client_id,
558 "redirect_uri" => redirect_uri,
559 "scope" => "read write",
560 "state" => "statepassed"
561 }
562 })
563
564 target = redirected_to(conn)
565 assert target =~ redirect_uri
566
567 query = URI.parse(target).query |> URI.query_decoder() |> Map.new()
568
569 assert %{"state" => "statepassed", "code" => code} = query
570 auth = Repo.get_by(Authorization, token: code)
571 assert auth
572 assert auth.scopes == ["read", "write"]
573 end
574
575 test "returns 401 for wrong credentials", %{conn: conn} do
576 user = insert(:user)
577 app = insert(:oauth_app)
578 redirect_uri = OAuthController.default_redirect_uri(app)
579
580 result =
581 conn
582 |> post("/oauth/authorize", %{
583 "authorization" => %{
584 "name" => user.nickname,
585 "password" => "wrong",
586 "client_id" => app.client_id,
587 "redirect_uri" => redirect_uri,
588 "state" => "statepassed",
589 "scope" => Enum.join(app.scopes, " ")
590 }
591 })
592 |> html_response(:unauthorized)
593
594 # Keep the details
595 assert result =~ app.client_id
596 assert result =~ redirect_uri
597
598 # Error message
599 assert result =~ "Invalid Username/Password"
600 end
601
602 test "returns 401 for missing scopes", %{conn: conn} do
603 user = insert(:user)
604 app = insert(:oauth_app)
605 redirect_uri = OAuthController.default_redirect_uri(app)
606
607 result =
608 conn
609 |> post("/oauth/authorize", %{
610 "authorization" => %{
611 "name" => user.nickname,
612 "password" => "test",
613 "client_id" => app.client_id,
614 "redirect_uri" => redirect_uri,
615 "state" => "statepassed",
616 "scope" => ""
617 }
618 })
619 |> html_response(:unauthorized)
620
621 # Keep the details
622 assert result =~ app.client_id
623 assert result =~ redirect_uri
624
625 # Error message
626 assert result =~ "This action is outside the authorized scopes"
627 end
628
629 test "returns 401 for scopes beyond app scopes", %{conn: conn} do
630 user = insert(:user)
631 app = insert(:oauth_app, scopes: ["read", "write"])
632 redirect_uri = OAuthController.default_redirect_uri(app)
633
634 result =
635 conn
636 |> post("/oauth/authorize", %{
637 "authorization" => %{
638 "name" => user.nickname,
639 "password" => "test",
640 "client_id" => app.client_id,
641 "redirect_uri" => redirect_uri,
642 "state" => "statepassed",
643 "scope" => "read write follow"
644 }
645 })
646 |> html_response(:unauthorized)
647
648 # Keep the details
649 assert result =~ app.client_id
650 assert result =~ redirect_uri
651
652 # Error message
653 assert result =~ "This action is outside the authorized scopes"
654 end
655 end
656
657 describe "POST /oauth/token" do
658 test "issues a token for an all-body request" do
659 user = insert(:user)
660 app = insert(:oauth_app, scopes: ["read", "write"])
661
662 {:ok, auth} = Authorization.create_authorization(app, user, ["write"])
663
664 conn =
665 build_conn()
666 |> post("/oauth/token", %{
667 "grant_type" => "authorization_code",
668 "code" => auth.token,
669 "redirect_uri" => OAuthController.default_redirect_uri(app),
670 "client_id" => app.client_id,
671 "client_secret" => app.client_secret
672 })
673
674 assert %{"access_token" => token, "me" => ap_id} = json_response(conn, 200)
675
676 token = Repo.get_by(Token, token: token)
677 assert token
678 assert token.scopes == auth.scopes
679 assert user.ap_id == ap_id
680 end
681
682 test "issues a token for `password` grant_type with valid credentials, with full permissions by default" do
683 password = "testpassword"
684 user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
685
686 app = insert(:oauth_app, scopes: ["read", "write"])
687
688 # Note: "scope" param is intentionally omitted
689 conn =
690 build_conn()
691 |> post("/oauth/token", %{
692 "grant_type" => "password",
693 "username" => user.nickname,
694 "password" => password,
695 "client_id" => app.client_id,
696 "client_secret" => app.client_secret
697 })
698
699 assert %{"access_token" => token} = json_response(conn, 200)
700
701 token = Repo.get_by(Token, token: token)
702 assert token
703 assert token.scopes == app.scopes
704 end
705
706 test "issues a token for request with HTTP basic auth client credentials" do
707 user = insert(:user)
708 app = insert(:oauth_app, scopes: ["scope1", "scope2", "scope3"])
709
710 {:ok, auth} = Authorization.create_authorization(app, user, ["scope1", "scope2"])
711 assert auth.scopes == ["scope1", "scope2"]
712
713 app_encoded =
714 (URI.encode_www_form(app.client_id) <> ":" <> URI.encode_www_form(app.client_secret))
715 |> Base.encode64()
716
717 conn =
718 build_conn()
719 |> put_req_header("authorization", "Basic " <> app_encoded)
720 |> post("/oauth/token", %{
721 "grant_type" => "authorization_code",
722 "code" => auth.token,
723 "redirect_uri" => OAuthController.default_redirect_uri(app)
724 })
725
726 assert %{"access_token" => token, "scope" => scope} = json_response(conn, 200)
727
728 assert scope == "scope1 scope2"
729
730 token = Repo.get_by(Token, token: token)
731 assert token
732 assert token.scopes == ["scope1", "scope2"]
733 end
734
735 test "issue a token for client_credentials grant type" do
736 app = insert(:oauth_app, scopes: ["read", "write"])
737
738 conn =
739 build_conn()
740 |> post("/oauth/token", %{
741 "grant_type" => "client_credentials",
742 "client_id" => app.client_id,
743 "client_secret" => app.client_secret
744 })
745
746 assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
747 json_response(conn, 200)
748
749 assert token
750 token_from_db = Repo.get_by(Token, token: token)
751 assert token_from_db
752 assert refresh
753 assert scope == "read write"
754 end
755
756 test "rejects token exchange with invalid client credentials" do
757 user = insert(:user)
758 app = insert(:oauth_app)
759
760 {:ok, auth} = Authorization.create_authorization(app, user)
761
762 conn =
763 build_conn()
764 |> put_req_header("authorization", "Basic JTIxOiVGMCU5RiVBNCVCNwo=")
765 |> post("/oauth/token", %{
766 "grant_type" => "authorization_code",
767 "code" => auth.token,
768 "redirect_uri" => OAuthController.default_redirect_uri(app)
769 })
770
771 assert resp = json_response(conn, 400)
772 assert %{"error" => _} = resp
773 refute Map.has_key?(resp, "access_token")
774 end
775
776 test "rejects token exchange for valid credentials belonging to unconfirmed user and confirmation is required" do
777 Pleroma.Config.put([:instance, :account_activation_required], true)
778
779 password = "testpassword"
780 user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
781 info_change = Pleroma.User.Info.confirmation_changeset(user.info, need_confirmation: true)
782
783 {:ok, user} =
784 user
785 |> Ecto.Changeset.change()
786 |> Ecto.Changeset.put_embed(:info, info_change)
787 |> Repo.update()
788
789 refute Pleroma.User.auth_active?(user)
790
791 app = insert(:oauth_app)
792
793 conn =
794 build_conn()
795 |> post("/oauth/token", %{
796 "grant_type" => "password",
797 "username" => user.nickname,
798 "password" => password,
799 "client_id" => app.client_id,
800 "client_secret" => app.client_secret
801 })
802
803 assert resp = json_response(conn, 403)
804 assert %{"error" => _} = resp
805 refute Map.has_key?(resp, "access_token")
806 end
807
808 test "rejects token exchange for valid credentials belonging to deactivated user" do
809 password = "testpassword"
810
811 user =
812 insert(:user,
813 password_hash: Comeonin.Pbkdf2.hashpwsalt(password),
814 info: %{deactivated: true}
815 )
816
817 app = insert(:oauth_app)
818
819 conn =
820 build_conn()
821 |> post("/oauth/token", %{
822 "grant_type" => "password",
823 "username" => user.nickname,
824 "password" => password,
825 "client_id" => app.client_id,
826 "client_secret" => app.client_secret
827 })
828
829 assert resp = json_response(conn, 403)
830 assert %{"error" => _} = resp
831 refute Map.has_key?(resp, "access_token")
832 end
833
834 test "rejects token exchange for user with password_reset_pending set to true" do
835 password = "testpassword"
836
837 user =
838 insert(:user,
839 password_hash: Comeonin.Pbkdf2.hashpwsalt(password),
840 info: %{password_reset_pending: true}
841 )
842
843 app = insert(:oauth_app, scopes: ["read", "write"])
844
845 conn =
846 build_conn()
847 |> post("/oauth/token", %{
848 "grant_type" => "password",
849 "username" => user.nickname,
850 "password" => password,
851 "client_id" => app.client_id,
852 "client_secret" => app.client_secret
853 })
854
855 assert resp = json_response(conn, 403)
856
857 assert resp["error"] == "Password reset is required"
858 refute Map.has_key?(resp, "access_token")
859 end
860
861 test "rejects an invalid authorization code" do
862 app = insert(:oauth_app)
863
864 conn =
865 build_conn()
866 |> post("/oauth/token", %{
867 "grant_type" => "authorization_code",
868 "code" => "Imobviouslyinvalid",
869 "redirect_uri" => OAuthController.default_redirect_uri(app),
870 "client_id" => app.client_id,
871 "client_secret" => app.client_secret
872 })
873
874 assert resp = json_response(conn, 400)
875 assert %{"error" => _} = json_response(conn, 400)
876 refute Map.has_key?(resp, "access_token")
877 end
878 end
879
880 describe "POST /oauth/token - refresh token" do
881 clear_config([:oauth2, :issue_new_refresh_token])
882
883 test "issues a new access token with keep fresh token" do
884 Pleroma.Config.put([:oauth2, :issue_new_refresh_token], true)
885 user = insert(:user)
886 app = insert(:oauth_app, scopes: ["read", "write"])
887
888 {:ok, auth} = Authorization.create_authorization(app, user, ["write"])
889 {:ok, token} = Token.exchange_token(app, auth)
890
891 response =
892 build_conn()
893 |> post("/oauth/token", %{
894 "grant_type" => "refresh_token",
895 "refresh_token" => token.refresh_token,
896 "client_id" => app.client_id,
897 "client_secret" => app.client_secret
898 })
899 |> json_response(200)
900
901 ap_id = user.ap_id
902
903 assert match?(
904 %{
905 "scope" => "write",
906 "token_type" => "Bearer",
907 "expires_in" => 600,
908 "access_token" => _,
909 "refresh_token" => _,
910 "me" => ^ap_id
911 },
912 response
913 )
914
915 refute Repo.get_by(Token, token: token.token)
916 new_token = Repo.get_by(Token, token: response["access_token"])
917 assert new_token.refresh_token == token.refresh_token
918 assert new_token.scopes == auth.scopes
919 assert new_token.user_id == user.id
920 assert new_token.app_id == app.id
921 end
922
923 test "issues a new access token with new fresh token" do
924 Pleroma.Config.put([:oauth2, :issue_new_refresh_token], false)
925 user = insert(:user)
926 app = insert(:oauth_app, scopes: ["read", "write"])
927
928 {:ok, auth} = Authorization.create_authorization(app, user, ["write"])
929 {:ok, token} = Token.exchange_token(app, auth)
930
931 response =
932 build_conn()
933 |> post("/oauth/token", %{
934 "grant_type" => "refresh_token",
935 "refresh_token" => token.refresh_token,
936 "client_id" => app.client_id,
937 "client_secret" => app.client_secret
938 })
939 |> json_response(200)
940
941 ap_id = user.ap_id
942
943 assert match?(
944 %{
945 "scope" => "write",
946 "token_type" => "Bearer",
947 "expires_in" => 600,
948 "access_token" => _,
949 "refresh_token" => _,
950 "me" => ^ap_id
951 },
952 response
953 )
954
955 refute Repo.get_by(Token, token: token.token)
956 new_token = Repo.get_by(Token, token: response["access_token"])
957 refute new_token.refresh_token == token.refresh_token
958 assert new_token.scopes == auth.scopes
959 assert new_token.user_id == user.id
960 assert new_token.app_id == app.id
961 end
962
963 test "returns 400 if we try use access token" do
964 user = insert(:user)
965 app = insert(:oauth_app, scopes: ["read", "write"])
966
967 {:ok, auth} = Authorization.create_authorization(app, user, ["write"])
968 {:ok, token} = Token.exchange_token(app, auth)
969
970 response =
971 build_conn()
972 |> post("/oauth/token", %{
973 "grant_type" => "refresh_token",
974 "refresh_token" => token.token,
975 "client_id" => app.client_id,
976 "client_secret" => app.client_secret
977 })
978 |> json_response(400)
979
980 assert %{"error" => "Invalid credentials"} == response
981 end
982
983 test "returns 400 if refresh_token invalid" do
984 app = insert(:oauth_app, scopes: ["read", "write"])
985
986 response =
987 build_conn()
988 |> post("/oauth/token", %{
989 "grant_type" => "refresh_token",
990 "refresh_token" => "token.refresh_token",
991 "client_id" => app.client_id,
992 "client_secret" => app.client_secret
993 })
994 |> json_response(400)
995
996 assert %{"error" => "Invalid credentials"} == response
997 end
998
999 test "issues a new token if token expired" do
1000 user = insert(:user)
1001 app = insert(:oauth_app, scopes: ["read", "write"])
1002
1003 {:ok, auth} = Authorization.create_authorization(app, user, ["write"])
1004 {:ok, token} = Token.exchange_token(app, auth)
1005
1006 change =
1007 Ecto.Changeset.change(
1008 token,
1009 %{valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), -86_400 * 30)}
1010 )
1011
1012 {:ok, access_token} = Repo.update(change)
1013
1014 response =
1015 build_conn()
1016 |> post("/oauth/token", %{
1017 "grant_type" => "refresh_token",
1018 "refresh_token" => access_token.refresh_token,
1019 "client_id" => app.client_id,
1020 "client_secret" => app.client_secret
1021 })
1022 |> json_response(200)
1023
1024 ap_id = user.ap_id
1025
1026 assert match?(
1027 %{
1028 "scope" => "write",
1029 "token_type" => "Bearer",
1030 "expires_in" => 600,
1031 "access_token" => _,
1032 "refresh_token" => _,
1033 "me" => ^ap_id
1034 },
1035 response
1036 )
1037
1038 refute Repo.get_by(Token, token: token.token)
1039 token = Repo.get_by(Token, token: response["access_token"])
1040 assert token
1041 assert token.scopes == auth.scopes
1042 assert token.user_id == user.id
1043 assert token.app_id == app.id
1044 end
1045 end
1046
1047 describe "POST /oauth/token - bad request" do
1048 test "returns 500" do
1049 response =
1050 build_conn()
1051 |> post("/oauth/token", %{})
1052 |> json_response(500)
1053
1054 assert %{"error" => "Bad request"} == response
1055 end
1056 end
1057
1058 describe "POST /oauth/revoke - bad request" do
1059 test "returns 500" do
1060 response =
1061 build_conn()
1062 |> post("/oauth/revoke", %{})
1063 |> json_response(500)
1064
1065 assert %{"error" => "Bad request"} == response
1066 end
1067 end
1068 end