Fork of Pleroma with site-specific changes and feature branches https://git.pleroma.social/pleroma/pleroma
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1042 lines
31KB

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