Pleroma.Web.TwitterAPI.TwoFactorAuthenticationController -> Pleroma.Web.PleromaAPI.TwoFactorAuthenticationController

This commit is contained in:
Maksim 2020-05-07 08:14:54 +00:00 committed by lain
commit 3d0c567fbc
49 changed files with 2184 additions and 34 deletions

View file

@ -0,0 +1,11 @@
defmodule Pleroma.MFA.BackupCodesTest do
use Pleroma.DataCase
alias Pleroma.MFA.BackupCodes
test "generate backup codes" do
codes = BackupCodes.generate(number_of_codes: 2, length: 4)
assert [<<_::bytes-size(4)>>, <<_::bytes-size(4)>>] = codes
end
end

17
test/mfa/totp_test.exs Normal file
View file

@ -0,0 +1,17 @@
defmodule Pleroma.MFA.TOTPTest do
use Pleroma.DataCase
alias Pleroma.MFA.TOTP
test "create provisioning_uri to generate qrcode" do
uri =
TOTP.provisioning_uri("test-secrcet", "test@example.com",
issuer: "Plerome-42",
digits: 8,
period: 60
)
assert uri ==
"otpauth://totp/test@example.com?digits=8&issuer=Plerome-42&period=60&secret=test-secrcet"
end
end

53
test/mfa_test.exs Normal file
View file

@ -0,0 +1,53 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.MFATest do
use Pleroma.DataCase
import Pleroma.Factory
alias Comeonin.Pbkdf2
alias Pleroma.MFA
describe "mfa_settings" do
test "returns settings user's" do
user =
insert(:user,
multi_factor_authentication_settings: %MFA.Settings{
enabled: true,
totp: %MFA.Settings.TOTP{secret: "xx", confirmed: true}
}
)
settings = MFA.mfa_settings(user)
assert match?(^settings, %{enabled: true, totp: true})
end
end
describe "generate backup codes" do
test "returns backup codes" do
user = insert(:user)
{:ok, [code1, code2]} = MFA.generate_backup_codes(user)
updated_user = refresh_record(user)
[hash1, hash2] = updated_user.multi_factor_authentication_settings.backup_codes
assert Pbkdf2.checkpw(code1, hash1)
assert Pbkdf2.checkpw(code2, hash2)
end
end
describe "invalidate_backup_code" do
test "invalid used code" do
user = insert(:user)
{:ok, _} = MFA.generate_backup_codes(user)
user = refresh_record(user)
assert length(user.multi_factor_authentication_settings.backup_codes) == 2
[hash_code | _] = user.multi_factor_authentication_settings.backup_codes
{:ok, user} = MFA.invalidate_backup_code(user, hash_code)
assert length(user.multi_factor_authentication_settings.backup_codes) == 1
end
end
end

View file

@ -24,6 +24,31 @@ defmodule Pleroma.Plugs.EnsureAuthenticatedPlugTest do
end
end
test "it halts if user is assigned and MFA enabled", %{conn: conn} do
conn =
conn
|> assign(:user, %User{multi_factor_authentication_settings: %{enabled: true}})
|> assign(:auth_credentials, %{password: "xd-42"})
|> EnsureAuthenticatedPlug.call(%{})
assert conn.status == 403
assert conn.halted == true
assert conn.resp_body ==
"{\"error\":\"Two-factor authentication enabled, you must use a access token.\"}"
end
test "it continues if user is assigned and MFA disabled", %{conn: conn} do
conn =
conn
|> assign(:user, %User{multi_factor_authentication_settings: %{enabled: false}})
|> assign(:auth_credentials, %{password: "xd-42"})
|> EnsureAuthenticatedPlug.call(%{})
refute conn.status == 403
refute conn.halted
end
describe "with :if_func / :unless_func options" do
setup do
%{

View file

@ -11,6 +11,7 @@ defmodule Pleroma.Builders.UserBuilder do
bio: "A tester.",
ap_id: "some id",
last_digest_emailed_at: NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),
multi_factor_authentication_settings: %Pleroma.MFA.Settings{},
notification_settings: %Pleroma.User.NotificationSetting{}
}

View file

@ -33,7 +33,8 @@ defmodule Pleroma.Factory do
bio: sequence(:bio, &"Tester Number #{&1}"),
last_digest_emailed_at: NaiveDateTime.utc_now(),
last_refreshed_at: NaiveDateTime.utc_now(),
notification_settings: %Pleroma.User.NotificationSetting{}
notification_settings: %Pleroma.User.NotificationSetting{},
multi_factor_authentication_settings: %Pleroma.MFA.Settings{}
}
%{
@ -422,4 +423,13 @@ defmodule Pleroma.Factory do
last_read_id: "1"
}
end
def mfa_token_factory do
%Pleroma.MFA.Token{
token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false),
authorization: build(:oauth_authorization),
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10),
user: build(:user)
}
end
end

View file

@ -172,6 +172,7 @@ defmodule Pleroma.UserSearchTest do
|> Map.put(:search_rank, nil)
|> Map.put(:search_type, nil)
|> Map.put(:last_digest_emailed_at, nil)
|> Map.put(:multi_factor_authentication_settings, nil)
|> Map.put(:notification_settings, nil)
assert user == expected

View file

@ -14,6 +14,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
alias Pleroma.Config
alias Pleroma.ConfigDB
alias Pleroma.HTML
alias Pleroma.MFA
alias Pleroma.ModerationLog
alias Pleroma.Repo
alias Pleroma.ReportNote
@ -1278,6 +1279,38 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"@#{admin.nickname} deactivated users: @#{user.nickname}"
end
describe "PUT disable_mfa" do
test "returns 200 and disable 2fa", %{conn: conn} do
user =
insert(:user,
multi_factor_authentication_settings: %MFA.Settings{
enabled: true,
totp: %MFA.Settings.TOTP{secret: "otp_secret", confirmed: true}
}
)
response =
conn
|> put("/api/pleroma/admin/users/disable_mfa", %{nickname: user.nickname})
|> json_response(200)
assert response == user.nickname
mfa_settings = refresh_record(user).multi_factor_authentication_settings
refute mfa_settings.enabled
refute mfa_settings.totp.confirmed
end
test "returns 404 if user not found", %{conn: conn} do
response =
conn
|> put("/api/pleroma/admin/users/disable_mfa", %{nickname: "nickname"})
|> json_response(404)
assert response == "Not found"
end
end
describe "POST /api/pleroma/admin/users/invite_token" do
test "without options", %{conn: conn} do
conn = post(conn, "/api/pleroma/admin/users/invite_token")

View file

@ -0,0 +1,43 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Auth.PleromaAuthenticatorTest do
use Pleroma.Web.ConnCase
alias Pleroma.Web.Auth.PleromaAuthenticator
import Pleroma.Factory
setup do
password = "testpassword"
name = "AgentSmith"
user = insert(:user, nickname: name, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
{:ok, [user: user, name: name, password: password]}
end
test "get_user/authorization", %{user: user, name: name, password: password} do
params = %{"authorization" => %{"name" => name, "password" => password}}
res = PleromaAuthenticator.get_user(%Plug.Conn{params: params})
assert {:ok, user} == res
end
test "get_user/authorization with invalid password", %{name: name} do
params = %{"authorization" => %{"name" => name, "password" => "password"}}
res = PleromaAuthenticator.get_user(%Plug.Conn{params: params})
assert {:error, {:checkpw, false}} == res
end
test "get_user/grant_type_password", %{user: user, name: name, password: password} do
params = %{"grant_type" => "password", "username" => name, "password" => password}
res = PleromaAuthenticator.get_user(%Plug.Conn{params: params})
assert {:ok, user} == res
end
test "error credintails" do
res = PleromaAuthenticator.get_user(%Plug.Conn{params: %{}})
assert {:error, :invalid_credentials} == res
end
end

View file

@ -0,0 +1,51 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Auth.TOTPAuthenticatorTest do
use Pleroma.Web.ConnCase
alias Pleroma.MFA
alias Pleroma.MFA.BackupCodes
alias Pleroma.MFA.TOTP
alias Pleroma.Web.Auth.TOTPAuthenticator
import Pleroma.Factory
test "verify token" do
otp_secret = TOTP.generate_secret()
otp_token = TOTP.generate_token(otp_secret)
user =
insert(:user,
multi_factor_authentication_settings: %MFA.Settings{
enabled: true,
totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
}
)
assert TOTPAuthenticator.verify(otp_token, user) == {:ok, :pass}
assert TOTPAuthenticator.verify(nil, user) == {:error, :invalid_token}
assert TOTPAuthenticator.verify("", user) == {:error, :invalid_token}
end
test "checks backup codes" do
[code | _] = backup_codes = BackupCodes.generate()
hashed_codes =
backup_codes
|> Enum.map(&Comeonin.Pbkdf2.hashpwsalt(&1))
user =
insert(:user,
multi_factor_authentication_settings: %MFA.Settings{
enabled: true,
backup_codes: hashed_codes,
totp: %MFA.Settings.TOTP{secret: "otp_secret", confirmed: true}
}
)
assert TOTPAuthenticator.verify_recovery_code(user, code) == {:ok, :pass}
refute TOTPAuthenticator.verify_recovery_code(code, refresh_record(user)) == {:ok, :pass}
end
end

View file

@ -0,0 +1,306 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.MFAControllerTest do
use Pleroma.Web.ConnCase
import Pleroma.Factory
alias Pleroma.MFA
alias Pleroma.MFA.BackupCodes
alias Pleroma.MFA.TOTP
alias Pleroma.Repo
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.OAuthController
setup %{conn: conn} do
otp_secret = TOTP.generate_secret()
user =
insert(:user,
multi_factor_authentication_settings: %MFA.Settings{
enabled: true,
backup_codes: [Comeonin.Pbkdf2.hashpwsalt("test-code")],
totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
}
)
app = insert(:oauth_app)
{:ok, conn: conn, user: user, app: app}
end
describe "show" do
setup %{conn: conn, user: user, app: app} do
mfa_token =
insert(:mfa_token,
user: user,
authorization: build(:oauth_authorization, app: app, scopes: ["write"])
)
{:ok, conn: conn, mfa_token: mfa_token}
end
test "GET /oauth/mfa renders mfa forms", %{conn: conn, mfa_token: mfa_token} do
conn =
get(
conn,
"/oauth/mfa",
%{
"mfa_token" => mfa_token.token,
"state" => "a_state",
"redirect_uri" => "http://localhost:8080/callback"
}
)
assert response = html_response(conn, 200)
assert response =~ "Two-factor authentication"
assert response =~ mfa_token.token
assert response =~ "http://localhost:8080/callback"
end
test "GET /oauth/mfa renders mfa recovery forms", %{conn: conn, mfa_token: mfa_token} do
conn =
get(
conn,
"/oauth/mfa",
%{
"mfa_token" => mfa_token.token,
"state" => "a_state",
"redirect_uri" => "http://localhost:8080/callback",
"challenge_type" => "recovery"
}
)
assert response = html_response(conn, 200)
assert response =~ "Two-factor recovery"
assert response =~ mfa_token.token
assert response =~ "http://localhost:8080/callback"
end
end
describe "verify" do
setup %{conn: conn, user: user, app: app} do
mfa_token =
insert(:mfa_token,
user: user,
authorization: build(:oauth_authorization, app: app, scopes: ["write"])
)
{:ok, conn: conn, user: user, mfa_token: mfa_token, app: app}
end
test "POST /oauth/mfa/verify, verify totp code", %{
conn: conn,
user: user,
mfa_token: mfa_token,
app: app
} do
otp_token = TOTP.generate_token(user.multi_factor_authentication_settings.totp.secret)
conn =
conn
|> post("/oauth/mfa/verify", %{
"mfa" => %{
"mfa_token" => mfa_token.token,
"challenge_type" => "totp",
"code" => otp_token,
"state" => "a_state",
"redirect_uri" => OAuthController.default_redirect_uri(app)
}
})
target = redirected_to(conn)
target_url = %URI{URI.parse(target) | query: nil} |> URI.to_string()
query = URI.parse(target).query |> URI.query_decoder() |> Map.new()
assert %{"state" => "a_state", "code" => code} = query
assert target_url == OAuthController.default_redirect_uri(app)
auth = Repo.get_by(Authorization, token: code)
assert auth.scopes == ["write"]
end
test "POST /oauth/mfa/verify, verify recovery code", %{
conn: conn,
mfa_token: mfa_token,
app: app
} do
conn =
conn
|> post("/oauth/mfa/verify", %{
"mfa" => %{
"mfa_token" => mfa_token.token,
"challenge_type" => "recovery",
"code" => "test-code",
"state" => "a_state",
"redirect_uri" => OAuthController.default_redirect_uri(app)
}
})
target = redirected_to(conn)
target_url = %URI{URI.parse(target) | query: nil} |> URI.to_string()
query = URI.parse(target).query |> URI.query_decoder() |> Map.new()
assert %{"state" => "a_state", "code" => code} = query
assert target_url == OAuthController.default_redirect_uri(app)
auth = Repo.get_by(Authorization, token: code)
assert auth.scopes == ["write"]
end
end
describe "challenge/totp" do
test "returns access token with valid code", %{conn: conn, user: user, app: app} do
otp_token = TOTP.generate_token(user.multi_factor_authentication_settings.totp.secret)
mfa_token =
insert(:mfa_token,
user: user,
authorization: build(:oauth_authorization, app: app, scopes: ["write"])
)
response =
conn
|> post("/oauth/mfa/challenge", %{
"mfa_token" => mfa_token.token,
"challenge_type" => "totp",
"code" => otp_token,
"client_id" => app.client_id,
"client_secret" => app.client_secret
})
|> json_response(:ok)
ap_id = user.ap_id
assert match?(
%{
"access_token" => _,
"expires_in" => 600,
"me" => ^ap_id,
"refresh_token" => _,
"scope" => "write",
"token_type" => "Bearer"
},
response
)
end
test "returns errors when mfa token invalid", %{conn: conn, user: user, app: app} do
otp_token = TOTP.generate_token(user.multi_factor_authentication_settings.totp.secret)
response =
conn
|> post("/oauth/mfa/challenge", %{
"mfa_token" => "XXX",
"challenge_type" => "totp",
"code" => otp_token,
"client_id" => app.client_id,
"client_secret" => app.client_secret
})
|> json_response(400)
assert response == %{"error" => "Invalid code"}
end
test "returns error when otp code is invalid", %{conn: conn, user: user, app: app} do
mfa_token = insert(:mfa_token, user: user)
response =
conn
|> post("/oauth/mfa/challenge", %{
"mfa_token" => mfa_token.token,
"challenge_type" => "totp",
"code" => "XXX",
"client_id" => app.client_id,
"client_secret" => app.client_secret
})
|> json_response(400)
assert response == %{"error" => "Invalid code"}
end
test "returns error when client credentails is wrong ", %{conn: conn, user: user} do
otp_token = TOTP.generate_token(user.multi_factor_authentication_settings.totp.secret)
mfa_token = insert(:mfa_token, user: user)
response =
conn
|> post("/oauth/mfa/challenge", %{
"mfa_token" => mfa_token.token,
"challenge_type" => "totp",
"code" => otp_token,
"client_id" => "xxx",
"client_secret" => "xxx"
})
|> json_response(400)
assert response == %{"error" => "Invalid code"}
end
end
describe "challenge/recovery" do
setup %{conn: conn} do
app = insert(:oauth_app)
{:ok, conn: conn, app: app}
end
test "returns access token with valid code", %{conn: conn, app: app} do
otp_secret = TOTP.generate_secret()
[code | _] = backup_codes = BackupCodes.generate()
hashed_codes =
backup_codes
|> Enum.map(&Comeonin.Pbkdf2.hashpwsalt(&1))
user =
insert(:user,
multi_factor_authentication_settings: %MFA.Settings{
enabled: true,
backup_codes: hashed_codes,
totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
}
)
mfa_token =
insert(:mfa_token,
user: user,
authorization: build(:oauth_authorization, app: app, scopes: ["write"])
)
response =
conn
|> post("/oauth/mfa/challenge", %{
"mfa_token" => mfa_token.token,
"challenge_type" => "recovery",
"code" => code,
"client_id" => app.client_id,
"client_secret" => app.client_secret
})
|> json_response(:ok)
ap_id = user.ap_id
assert match?(
%{
"access_token" => _,
"expires_in" => 600,
"me" => ^ap_id,
"refresh_token" => _,
"scope" => "write",
"token_type" => "Bearer"
},
response
)
error_response =
conn
|> post("/oauth/mfa/challenge", %{
"mfa_token" => mfa_token.token,
"challenge_type" => "recovery",
"code" => code,
"client_id" => app.client_id,
"client_secret" => app.client_secret
})
|> json_response(400)
assert error_response == %{"error" => "Invalid code"}
end
end
end

View file

@ -6,6 +6,8 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
use Pleroma.Web.ConnCase
import Pleroma.Factory
alias Pleroma.MFA
alias Pleroma.MFA.TOTP
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.OAuth.Authorization
@ -604,6 +606,41 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
end
end
test "redirect to on two-factor auth page" do
otp_secret = TOTP.generate_secret()
user =
insert(:user,
multi_factor_authentication_settings: %MFA.Settings{
enabled: true,
totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
}
)
app = insert(:oauth_app, scopes: ["read", "write", "follow"])
conn =
build_conn()
|> post("/oauth/authorize", %{
"authorization" => %{
"name" => user.nickname,
"password" => "test",
"client_id" => app.client_id,
"redirect_uri" => app.redirect_uris,
"scope" => "read write",
"state" => "statepassed"
}
})
result = html_response(conn, 200)
mfa_token = Repo.get_by(MFA.Token, user_id: user.id)
assert result =~ app.redirect_uris
assert result =~ "statepassed"
assert result =~ mfa_token.token
assert result =~ "Two-factor authentication"
end
test "returns 401 for wrong credentials", %{conn: conn} do
user = insert(:user)
app = insert(:oauth_app)
@ -735,6 +772,46 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
assert token.scopes == app.scopes
end
test "issues a mfa token for `password` grant_type, when MFA enabled" do
password = "testpassword"
otp_secret = TOTP.generate_secret()
user =
insert(:user,
password_hash: Comeonin.Pbkdf2.hashpwsalt(password),
multi_factor_authentication_settings: %MFA.Settings{
enabled: true,
totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
}
)
app = insert(:oauth_app, scopes: ["read", "write"])
response =
build_conn()
|> post("/oauth/token", %{
"grant_type" => "password",
"username" => user.nickname,
"password" => password,
"client_id" => app.client_id,
"client_secret" => app.client_secret
})
|> json_response(403)
assert match?(
%{
"supported_challenge_types" => "totp",
"mfa_token" => _,
"error" => "mfa_required"
},
response
)
token = Repo.get_by(MFA.Token, token: response["mfa_token"])
assert token.user_id == user.id
assert token.authorization_id
end
test "issues a token for request with HTTP basic auth client credentials" do
user = insert(:user)
app = insert(:oauth_app, scopes: ["scope1", "scope2", "scope3"])

View file

@ -0,0 +1,260 @@
defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationControllerTest do
use Pleroma.Web.ConnCase
import Pleroma.Factory
alias Pleroma.MFA.Settings
alias Pleroma.MFA.TOTP
describe "GET /api/pleroma/accounts/mfa/settings" do
test "returns user mfa settings for new user", %{conn: conn} do
token = insert(:oauth_token, scopes: ["read", "follow"])
token2 = insert(:oauth_token, scopes: ["write"])
assert conn
|> put_req_header("authorization", "Bearer #{token.token}")
|> get("/api/pleroma/accounts/mfa")
|> json_response(:ok) == %{
"settings" => %{"enabled" => false, "totp" => false}
}
assert conn
|> put_req_header("authorization", "Bearer #{token2.token}")
|> get("/api/pleroma/accounts/mfa")
|> json_response(403) == %{
"error" => "Insufficient permissions: read:security."
}
end
test "returns user mfa settings with enabled totp", %{conn: conn} do
user =
insert(:user,
multi_factor_authentication_settings: %Settings{
enabled: true,
totp: %Settings.TOTP{secret: "XXX", delivery_type: "app", confirmed: true}
}
)
token = insert(:oauth_token, scopes: ["read", "follow"], user: user)
assert conn
|> put_req_header("authorization", "Bearer #{token.token}")
|> get("/api/pleroma/accounts/mfa")
|> json_response(:ok) == %{
"settings" => %{"enabled" => true, "totp" => true}
}
end
end
describe "GET /api/pleroma/accounts/mfa/backup_codes" do
test "returns backup codes", %{conn: conn} do
user =
insert(:user,
multi_factor_authentication_settings: %Settings{
backup_codes: ["1", "2", "3"],
totp: %Settings.TOTP{secret: "secret"}
}
)
token = insert(:oauth_token, scopes: ["write", "follow"], user: user)
token2 = insert(:oauth_token, scopes: ["read"])
response =
conn
|> put_req_header("authorization", "Bearer #{token.token}")
|> get("/api/pleroma/accounts/mfa/backup_codes")
|> json_response(:ok)
assert [<<_::bytes-size(6)>>, <<_::bytes-size(6)>>] = response["codes"]
user = refresh_record(user)
mfa_settings = user.multi_factor_authentication_settings
assert mfa_settings.totp.secret == "secret"
refute mfa_settings.backup_codes == ["1", "2", "3"]
refute mfa_settings.backup_codes == []
assert conn
|> put_req_header("authorization", "Bearer #{token2.token}")
|> get("/api/pleroma/accounts/mfa/backup_codes")
|> json_response(403) == %{
"error" => "Insufficient permissions: write:security."
}
end
end
describe "GET /api/pleroma/accounts/mfa/setup/totp" do
test "return errors when method is invalid", %{conn: conn} do
user = insert(:user)
token = insert(:oauth_token, scopes: ["write", "follow"], user: user)
response =
conn
|> put_req_header("authorization", "Bearer #{token.token}")
|> get("/api/pleroma/accounts/mfa/setup/torf")
|> json_response(400)
assert response == %{"error" => "undefined method"}
end
test "returns key and provisioning_uri", %{conn: conn} do
user =
insert(:user,
multi_factor_authentication_settings: %Settings{backup_codes: ["1", "2", "3"]}
)
token = insert(:oauth_token, scopes: ["write", "follow"], user: user)
token2 = insert(:oauth_token, scopes: ["read"])
response =
conn
|> put_req_header("authorization", "Bearer #{token.token}")
|> get("/api/pleroma/accounts/mfa/setup/totp")
|> json_response(:ok)
user = refresh_record(user)
mfa_settings = user.multi_factor_authentication_settings
secret = mfa_settings.totp.secret
refute mfa_settings.enabled
assert mfa_settings.backup_codes == ["1", "2", "3"]
assert response == %{
"key" => secret,
"provisioning_uri" => TOTP.provisioning_uri(secret, "#{user.email}")
}
assert conn
|> put_req_header("authorization", "Bearer #{token2.token}")
|> get("/api/pleroma/accounts/mfa/setup/totp")
|> json_response(403) == %{
"error" => "Insufficient permissions: write:security."
}
end
end
describe "GET /api/pleroma/accounts/mfa/confirm/totp" do
test "returns success result", %{conn: conn} do
secret = TOTP.generate_secret()
code = TOTP.generate_token(secret)
user =
insert(:user,
multi_factor_authentication_settings: %Settings{
backup_codes: ["1", "2", "3"],
totp: %Settings.TOTP{secret: secret}
}
)
token = insert(:oauth_token, scopes: ["write", "follow"], user: user)
token2 = insert(:oauth_token, scopes: ["read"])
assert conn
|> put_req_header("authorization", "Bearer #{token.token}")
|> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "test", code: code})
|> json_response(:ok)
settings = refresh_record(user).multi_factor_authentication_settings
assert settings.enabled
assert settings.totp.secret == secret
assert settings.totp.confirmed
assert settings.backup_codes == ["1", "2", "3"]
assert conn
|> put_req_header("authorization", "Bearer #{token2.token}")
|> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "test", code: code})
|> json_response(403) == %{
"error" => "Insufficient permissions: write:security."
}
end
test "returns error if password incorrect", %{conn: conn} do
secret = TOTP.generate_secret()
code = TOTP.generate_token(secret)
user =
insert(:user,
multi_factor_authentication_settings: %Settings{
backup_codes: ["1", "2", "3"],
totp: %Settings.TOTP{secret: secret}
}
)
token = insert(:oauth_token, scopes: ["write", "follow"], user: user)
response =
conn
|> put_req_header("authorization", "Bearer #{token.token}")
|> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "xxx", code: code})
|> json_response(422)
settings = refresh_record(user).multi_factor_authentication_settings
refute settings.enabled
refute settings.totp.confirmed
assert settings.backup_codes == ["1", "2", "3"]
assert response == %{"error" => "Invalid password."}
end
test "returns error if code incorrect", %{conn: conn} do
secret = TOTP.generate_secret()
user =
insert(:user,
multi_factor_authentication_settings: %Settings{
backup_codes: ["1", "2", "3"],
totp: %Settings.TOTP{secret: secret}
}
)
token = insert(:oauth_token, scopes: ["write", "follow"], user: user)
token2 = insert(:oauth_token, scopes: ["read"])
response =
conn
|> put_req_header("authorization", "Bearer #{token.token}")
|> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "test", code: "code"})
|> json_response(422)
settings = refresh_record(user).multi_factor_authentication_settings
refute settings.enabled
refute settings.totp.confirmed
assert settings.backup_codes == ["1", "2", "3"]
assert response == %{"error" => "invalid_token"}
assert conn
|> put_req_header("authorization", "Bearer #{token2.token}")
|> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "test", code: "code"})
|> json_response(403) == %{
"error" => "Insufficient permissions: write:security."
}
end
end
describe "DELETE /api/pleroma/accounts/mfa/totp" do
test "returns success result", %{conn: conn} do
user =
insert(:user,
multi_factor_authentication_settings: %Settings{
backup_codes: ["1", "2", "3"],
totp: %Settings.TOTP{secret: "secret"}
}
)
token = insert(:oauth_token, scopes: ["write", "follow"], user: user)
token2 = insert(:oauth_token, scopes: ["read"])
assert conn
|> put_req_header("authorization", "Bearer #{token.token}")
|> delete("/api/pleroma/accounts/mfa/totp", %{password: "test"})
|> json_response(:ok)
settings = refresh_record(user).multi_factor_authentication_settings
refute settings.enabled
assert settings.totp.secret == nil
refute settings.totp.confirmed
assert conn
|> put_req_header("authorization", "Bearer #{token2.token}")
|> delete("/api/pleroma/accounts/mfa/totp", %{password: "test"})
|> json_response(403) == %{
"error" => "Insufficient permissions: write:security."
}
end
end
end

View file

@ -6,11 +6,14 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do
use Pleroma.Web.ConnCase
alias Pleroma.Config
alias Pleroma.MFA
alias Pleroma.MFA.TOTP
alias Pleroma.User
alias Pleroma.Web.CommonAPI
import ExUnit.CaptureLog
import Pleroma.Factory
import Ecto.Query
setup do
Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
@ -160,6 +163,119 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do
end
end
describe "POST /ostatus_subscribe - follow/2 with enabled Two-Factor Auth " do
test "render the MFA login form", %{conn: conn} do
otp_secret = TOTP.generate_secret()
user =
insert(:user,
multi_factor_authentication_settings: %MFA.Settings{
enabled: true,
totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
}
)
user2 = insert(:user)
response =
conn
|> post(remote_follow_path(conn, :do_follow), %{
"authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id}
})
|> response(200)
mfa_token = Pleroma.Repo.one(from(q in Pleroma.MFA.Token, where: q.user_id == ^user.id))
assert response =~ "Two-factor authentication"
assert response =~ "Authentication code"
assert response =~ mfa_token.token
refute user2.follower_address in User.following(user)
end
test "returns error when password is incorrect", %{conn: conn} do
otp_secret = TOTP.generate_secret()
user =
insert(:user,
multi_factor_authentication_settings: %MFA.Settings{
enabled: true,
totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
}
)
user2 = insert(:user)
response =
conn
|> post(remote_follow_path(conn, :do_follow), %{
"authorization" => %{"name" => user.nickname, "password" => "test1", "id" => user2.id}
})
|> response(200)
assert response =~ "Wrong username or password"
refute user2.follower_address in User.following(user)
end
test "follows", %{conn: conn} do
otp_secret = TOTP.generate_secret()
user =
insert(:user,
multi_factor_authentication_settings: %MFA.Settings{
enabled: true,
totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
}
)
{:ok, %{token: token}} = MFA.Token.create_token(user)
user2 = insert(:user)
otp_token = TOTP.generate_token(otp_secret)
conn =
conn
|> post(
remote_follow_path(conn, :do_follow),
%{
"mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id}
}
)
assert redirected_to(conn) == "/users/#{user2.id}"
assert user2.follower_address in User.following(user)
end
test "returns error when auth code is incorrect", %{conn: conn} do
otp_secret = TOTP.generate_secret()
user =
insert(:user,
multi_factor_authentication_settings: %MFA.Settings{
enabled: true,
totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
}
)
{:ok, %{token: token}} = MFA.Token.create_token(user)
user2 = insert(:user)
otp_token = TOTP.generate_token(TOTP.generate_secret())
response =
conn
|> post(
remote_follow_path(conn, :do_follow),
%{
"mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id}
}
)
|> response(200)
assert response =~ "Wrong authentication code"
refute user2.follower_address in User.following(user)
end
end
describe "POST /ostatus_subscribe - follow/2 without assigned user " do
test "follows", %{conn: conn} do
user = insert(:user)