Merge branch 'feature/767-multiple-use-invite-token' into 'develop'

Feature/767 multiple use invite token

See merge request pleroma/pleroma!1032
This commit is contained in:
lambda 2019-04-10 10:10:08 +00:00
commit e5d553aa45
14 changed files with 1003 additions and 112 deletions

64
test/fixtures/lambadalambda.json vendored Normal file
View file

@ -0,0 +1,64 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"toot": "http://joinmastodon.org/ns#",
"featured": {
"@id": "toot:featured",
"@type": "@id"
},
"alsoKnownAs": {
"@id": "as:alsoKnownAs",
"@type": "@id"
},
"movedTo": {
"@id": "as:movedTo",
"@type": "@id"
},
"schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value",
"Hashtag": "as:Hashtag",
"Emoji": "toot:Emoji",
"IdentityProof": "toot:IdentityProof",
"focalPoint": {
"@container": "@list",
"@id": "toot:focalPoint"
}
}
],
"id": "https://mastodon.social/users/lambadalambda",
"type": "Person",
"following": "https://mastodon.social/users/lambadalambda/following",
"followers": "https://mastodon.social/users/lambadalambda/followers",
"inbox": "https://mastodon.social/users/lambadalambda/inbox",
"outbox": "https://mastodon.social/users/lambadalambda/outbox",
"featured": "https://mastodon.social/users/lambadalambda/collections/featured",
"preferredUsername": "lambadalambda",
"name": "Critical Value",
"summary": "\u003cp\u003e\u003c/p\u003e",
"url": "https://mastodon.social/@lambadalambda",
"manuallyApprovesFollowers": false,
"publicKey": {
"id": "https://mastodon.social/users/lambadalambda#main-key",
"owner": "https://mastodon.social/users/lambadalambda",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw0P/Tq4gb4G/QVuMGbJo\nC/AfMNcv+m7NfrlOwkVzcU47jgESuYI4UtJayissCdBycHUnfVUd9qol+eznSODz\nCJhfJloqEIC+aSnuEPGA0POtWad6DU0E6/Ho5zQn5WAWUwbRQqowbrsm/GHo2+3v\neR5jGenwA6sYhINg/c3QQbksyV0uJ20Umyx88w8+TJuv53twOfmyDWuYNoQ3y5cc\nHKOZcLHxYOhvwg3PFaGfFHMFiNmF40dTXt9K96r7sbzc44iLD+VphbMPJEjkMuf8\nPGEFOBzy8pm3wJZw2v32RNW2VESwMYyqDzwHXGSq1a73cS7hEnc79gXlELsK04L9\nQQIDAQAB\n-----END PUBLIC KEY-----\n"
},
"tag": [],
"attachment": [],
"endpoints": {
"sharedInbox": "https://mastodon.social/inbox"
},
"icon": {
"type": "Image",
"mediaType": "image/gif",
"url": "https://files.mastodon.social/accounts/avatars/000/000/264/original/1429214160519.gif"
},
"image": {
"type": "Image",
"mediaType": "image/gif",
"url": "https://files.mastodon.social/accounts/headers/000/000/264/original/28b26104f83747d2.gif"
}
}

View file

@ -716,6 +716,10 @@ defmodule HttpRequestMock do
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/lambadalambda.atom")}}
end
def get("https://mastodon.social/users/lambadalambda", _, _, _) do
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/lambadalambda.json")}}
end
def get("https://social.heldscal.la/user/23211", _, _, Accept: "application/activity+json") do
{:ok, Tesla.Mock.json(%{"id" => "https://social.heldscal.la/user/23211"}, status: 200)}
end

View file

@ -245,7 +245,87 @@ defmodule Mix.Tasks.Pleroma.UserTest do
end) =~ "http"
assert_received {:mix_shell, :info, [message]}
assert message =~ "Generated"
assert message =~ "Generated user invite token one time"
end
test "token is generated with expires_at" do
assert capture_io(fn ->
Mix.Tasks.Pleroma.User.run([
"invite",
"--expires-at",
Date.to_string(Date.utc_today())
])
end)
assert_received {:mix_shell, :info, [message]}
assert message =~ "Generated user invite token date limited"
end
test "token is generated with max use" do
assert capture_io(fn ->
Mix.Tasks.Pleroma.User.run([
"invite",
"--max-use",
"5"
])
end)
assert_received {:mix_shell, :info, [message]}
assert message =~ "Generated user invite token reusable"
end
test "token is generated with max use and expires date" do
assert capture_io(fn ->
Mix.Tasks.Pleroma.User.run([
"invite",
"--max-use",
"5",
"--expires-at",
Date.to_string(Date.utc_today())
])
end)
assert_received {:mix_shell, :info, [message]}
assert message =~ "Generated user invite token reusable date limited"
end
end
describe "running invites" do
test "invites are listed" do
{:ok, invite} = Pleroma.UserInviteToken.create_invite()
{:ok, invite2} =
Pleroma.UserInviteToken.create_invite(%{expires_at: Date.utc_today(), max_use: 15})
# assert capture_io(fn ->
Mix.Tasks.Pleroma.User.run([
"invites"
])
# end)
assert_received {:mix_shell, :info, [message]}
assert_received {:mix_shell, :info, [message2]}
assert_received {:mix_shell, :info, [message3]}
assert message =~ "Invites list:"
assert message2 =~ invite.invite_type
assert message3 =~ invite2.invite_type
end
end
describe "running revoke_invite" do
test "invite is revoked" do
{:ok, invite} = Pleroma.UserInviteToken.create_invite(%{expires_at: Date.utc_today()})
assert capture_io(fn ->
Mix.Tasks.Pleroma.User.run([
"revoke_invite",
invite.token
])
end)
assert_received {:mix_shell, :info, [message]}
assert message =~ "Invite for token #{invite.token} was revoked."
end
end

View file

@ -0,0 +1,96 @@
defmodule Pleroma.UserInviteTokenTest do
use ExUnit.Case, async: true
use Pleroma.DataCase
alias Pleroma.UserInviteToken
describe "valid_invite?/1 one time invites" do
setup do
invite = %UserInviteToken{invite_type: "one_time"}
{:ok, invite: invite}
end
test "not used returns true", %{invite: invite} do
invite = %{invite | used: false}
assert UserInviteToken.valid_invite?(invite)
end
test "used returns false", %{invite: invite} do
invite = %{invite | used: true}
refute UserInviteToken.valid_invite?(invite)
end
end
describe "valid_invite?/1 reusable invites" do
setup do
invite = %UserInviteToken{
invite_type: "reusable",
max_use: 5
}
{:ok, invite: invite}
end
test "with less uses then max use returns true", %{invite: invite} do
invite = %{invite | uses: 4}
assert UserInviteToken.valid_invite?(invite)
end
test "with equal or more uses then max use returns false", %{invite: invite} do
invite = %{invite | uses: 5}
refute UserInviteToken.valid_invite?(invite)
invite = %{invite | uses: 6}
refute UserInviteToken.valid_invite?(invite)
end
end
describe "valid_token?/1 date limited invites" do
setup do
invite = %UserInviteToken{invite_type: "date_limited"}
{:ok, invite: invite}
end
test "expires today returns true", %{invite: invite} do
invite = %{invite | expires_at: Date.utc_today()}
assert UserInviteToken.valid_invite?(invite)
end
test "expires yesterday returns false", %{invite: invite} do
invite = %{invite | expires_at: Date.add(Date.utc_today(), -1)}
invite = Repo.insert!(invite)
refute UserInviteToken.valid_invite?(invite)
end
end
describe "valid_token?/1 reusable date limited invites" do
setup do
invite = %UserInviteToken{invite_type: "reusable_date_limited", max_use: 5}
{:ok, invite: invite}
end
test "not overdue date and less uses returns true", %{invite: invite} do
invite = %{invite | expires_at: Date.utc_today(), uses: 4}
assert UserInviteToken.valid_invite?(invite)
end
test "overdue date and less uses returns false", %{invite: invite} do
invite = %{invite | expires_at: Date.add(Date.utc_today(), -1)}
invite = Repo.insert!(invite)
refute UserInviteToken.valid_invite?(invite)
end
test "not overdue date with more uses returns false", %{invite: invite} do
invite = %{invite | expires_at: Date.utc_today(), uses: 5}
refute UserInviteToken.valid_invite?(invite)
end
test "overdue date with more uses returns false", %{invite: invite} do
invite = %{invite | expires_at: Date.add(Date.utc_today(), -1), uses: 5}
invite = Repo.insert!(invite)
refute UserInviteToken.valid_invite?(invite)
end
end
end

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
use Pleroma.Web.ConnCase
alias Pleroma.User
alias Pleroma.UserInviteToken
import Pleroma.Factory
describe "/api/pleroma/admin/user" do
@ -640,4 +641,136 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => []
}
end
describe "GET /api/pleroma/admin/invite_token" do
test "without options" do
admin = insert(:user, info: %{is_admin: true})
conn =
build_conn()
|> assign(:user, admin)
|> get("/api/pleroma/admin/invite_token")
token = json_response(conn, 200)
invite = UserInviteToken.find_by_token!(token)
refute invite.used
refute invite.expires_at
refute invite.max_use
assert invite.invite_type == "one_time"
end
test "with expires_at" do
admin = insert(:user, info: %{is_admin: true})
conn =
build_conn()
|> assign(:user, admin)
|> get("/api/pleroma/admin/invite_token", %{
"invite" => %{"expires_at" => Date.to_string(Date.utc_today())}
})
token = json_response(conn, 200)
invite = UserInviteToken.find_by_token!(token)
refute invite.used
assert invite.expires_at == Date.utc_today()
refute invite.max_use
assert invite.invite_type == "date_limited"
end
test "with max_use" do
admin = insert(:user, info: %{is_admin: true})
conn =
build_conn()
|> assign(:user, admin)
|> get("/api/pleroma/admin/invite_token", %{
"invite" => %{"max_use" => 150}
})
token = json_response(conn, 200)
invite = UserInviteToken.find_by_token!(token)
refute invite.used
refute invite.expires_at
assert invite.max_use == 150
assert invite.invite_type == "reusable"
end
test "with max use and expires_at" do
admin = insert(:user, info: %{is_admin: true})
conn =
build_conn()
|> assign(:user, admin)
|> get("/api/pleroma/admin/invite_token", %{
"invite" => %{"max_use" => 150, "expires_at" => Date.to_string(Date.utc_today())}
})
token = json_response(conn, 200)
invite = UserInviteToken.find_by_token!(token)
refute invite.used
assert invite.expires_at == Date.utc_today()
assert invite.max_use == 150
assert invite.invite_type == "reusable_date_limited"
end
end
describe "GET /api/pleroma/admin/invites" do
test "no invites" do
admin = insert(:user, info: %{is_admin: true})
conn =
build_conn()
|> assign(:user, admin)
|> get("/api/pleroma/admin/invites")
assert json_response(conn, 200) == %{"invites" => []}
end
test "with invite" do
admin = insert(:user, info: %{is_admin: true})
{:ok, invite} = UserInviteToken.create_invite()
conn =
build_conn()
|> assign(:user, admin)
|> get("/api/pleroma/admin/invites")
assert json_response(conn, 200) == %{
"invites" => [
%{
"expires_at" => nil,
"id" => invite.id,
"invite_type" => "one_time",
"max_use" => nil,
"token" => invite.token,
"used" => false,
"uses" => 0
}
]
}
end
end
describe "POST /api/pleroma/admin/revoke_invite" do
test "with token" do
admin = insert(:user, info: %{is_admin: true})
{:ok, invite} = UserInviteToken.create_invite()
conn =
build_conn()
|> assign(:user, admin)
|> post("/api/pleroma/admin/revoke_invite", %{"token" => invite.token})
assert json_response(conn, 200) == %{
"expires_at" => nil,
"id" => invite.id,
"invite_type" => "one_time",
"max_use" => nil,
"token" => invite.token,
"used" => true,
"uses" => 0
}
end
end
end

View file

@ -16,6 +16,11 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
import Pleroma.Factory
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
end
test "create a status" do
user = insert(:user)
mentioned_user = insert(:user, %{nickname: "shp", ap_id: "shp"})
@ -299,7 +304,6 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
UserView.render("show.json", %{user: fetched_user})
end
@moduletag skip: "needs 'account_activation_required: true' in config"
test "it sends confirmation email if :account_activation_required is specified in instance config" do
setting = Pleroma.Config.get([:instance, :account_activation_required])
@ -353,68 +357,313 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
assert user2.bio == expected_text
end
@moduletag skip: "needs 'registrations_open: false' in config"
test "it registers a new user via invite token and returns the user." do
{:ok, token} = UserInviteToken.create_token()
describe "register with one time token" do
setup do
setting = Pleroma.Config.get([:instance, :registrations_open])
data = %{
"nickname" => "vinny",
"email" => "pasta@pizza.vs",
"fullname" => "Vinny Vinesauce",
"bio" => "streamer",
"password" => "hiptofbees",
"confirm" => "hiptofbees",
"token" => token.token
}
if setting do
Pleroma.Config.put([:instance, :registrations_open], false)
on_exit(fn -> Pleroma.Config.put([:instance, :registrations_open], setting) end)
end
{:ok, user} = TwitterAPI.register_user(data)
:ok
end
fetched_user = User.get_by_nickname("vinny")
token = Repo.get_by(UserInviteToken, token: token.token)
test "returns user on success" do
{:ok, invite} = UserInviteToken.create_invite()
assert token.used == true
data = %{
"nickname" => "vinny",
"email" => "pasta@pizza.vs",
"fullname" => "Vinny Vinesauce",
"bio" => "streamer",
"password" => "hiptofbees",
"confirm" => "hiptofbees",
"token" => invite.token
}
assert UserView.render("show.json", %{user: user}) ==
UserView.render("show.json", %{user: fetched_user})
{:ok, user} = TwitterAPI.register_user(data)
fetched_user = User.get_by_nickname("vinny")
invite = Repo.get_by(UserInviteToken, token: invite.token)
assert invite.used == true
assert UserView.render("show.json", %{user: user}) ==
UserView.render("show.json", %{user: fetched_user})
end
test "returns error on invalid token" do
data = %{
"nickname" => "GrimReaper",
"email" => "death@reapers.afterlife",
"fullname" => "Reaper Grim",
"bio" => "Your time has come",
"password" => "scythe",
"confirm" => "scythe",
"token" => "DudeLetMeInImAFairy"
}
{:error, msg} = TwitterAPI.register_user(data)
assert msg == "Invalid token"
refute User.get_by_nickname("GrimReaper")
end
test "returns error on expired token" do
{:ok, invite} = UserInviteToken.create_invite()
UserInviteToken.update_invite!(invite, used: true)
data = %{
"nickname" => "GrimReaper",
"email" => "death@reapers.afterlife",
"fullname" => "Reaper Grim",
"bio" => "Your time has come",
"password" => "scythe",
"confirm" => "scythe",
"token" => invite.token
}
{:error, msg} = TwitterAPI.register_user(data)
assert msg == "Expired token"
refute User.get_by_nickname("GrimReaper")
end
end
@moduletag skip: "needs 'registrations_open: false' in config"
test "it returns an error if invalid token submitted" do
data = %{
"nickname" => "GrimReaper",
"email" => "death@reapers.afterlife",
"fullname" => "Reaper Grim",
"bio" => "Your time has come",
"password" => "scythe",
"confirm" => "scythe",
"token" => "DudeLetMeInImAFairy"
}
describe "registers with date limited token" do
setup do
setting = Pleroma.Config.get([:instance, :registrations_open])
{:error, msg} = TwitterAPI.register_user(data)
if setting do
Pleroma.Config.put([:instance, :registrations_open], false)
on_exit(fn -> Pleroma.Config.put([:instance, :registrations_open], setting) end)
end
assert msg == "Invalid token"
refute User.get_by_nickname("GrimReaper")
data = %{
"nickname" => "vinny",
"email" => "pasta@pizza.vs",
"fullname" => "Vinny Vinesauce",
"bio" => "streamer",
"password" => "hiptofbees",
"confirm" => "hiptofbees"
}
check_fn = fn invite ->
data = Map.put(data, "token", invite.token)
{:ok, user} = TwitterAPI.register_user(data)
fetched_user = User.get_by_nickname("vinny")
assert UserView.render("show.json", %{user: user}) ==
UserView.render("show.json", %{user: fetched_user})
end
{:ok, data: data, check_fn: check_fn}
end
test "returns user on success", %{check_fn: check_fn} do
{:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.utc_today()})
check_fn.(invite)
invite = Repo.get_by(UserInviteToken, token: invite.token)
refute invite.used
end
test "returns user on token which expired tomorrow", %{check_fn: check_fn} do
{:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), 1)})
check_fn.(invite)
invite = Repo.get_by(UserInviteToken, token: invite.token)
refute invite.used
end
test "returns an error on overdue date", %{data: data} do
{:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), -1)})
data = Map.put(data, "token", invite.token)
{:error, msg} = TwitterAPI.register_user(data)
assert msg == "Expired token"
refute User.get_by_nickname("vinny")
invite = Repo.get_by(UserInviteToken, token: invite.token)
refute invite.used
end
end
@moduletag skip: "needs 'registrations_open: false' in config"
test "it returns an error if expired token submitted" do
{:ok, token} = UserInviteToken.create_token()
UserInviteToken.mark_as_used(token.token)
describe "registers with reusable token" do
setup do
setting = Pleroma.Config.get([:instance, :registrations_open])
data = %{
"nickname" => "GrimReaper",
"email" => "death@reapers.afterlife",
"fullname" => "Reaper Grim",
"bio" => "Your time has come",
"password" => "scythe",
"confirm" => "scythe",
"token" => token.token
}
if setting do
Pleroma.Config.put([:instance, :registrations_open], false)
on_exit(fn -> Pleroma.Config.put([:instance, :registrations_open], setting) end)
end
{:error, msg} = TwitterAPI.register_user(data)
:ok
end
assert msg == "Expired token"
refute User.get_by_nickname("GrimReaper")
test "returns user on success, after him registration fails" do
{:ok, invite} = UserInviteToken.create_invite(%{max_use: 100})
UserInviteToken.update_invite!(invite, uses: 99)
data = %{
"nickname" => "vinny",
"email" => "pasta@pizza.vs",
"fullname" => "Vinny Vinesauce",
"bio" => "streamer",
"password" => "hiptofbees",
"confirm" => "hiptofbees",
"token" => invite.token
}
{:ok, user} = TwitterAPI.register_user(data)
fetched_user = User.get_by_nickname("vinny")
invite = Repo.get_by(UserInviteToken, token: invite.token)
assert invite.used == true
assert UserView.render("show.json", %{user: user}) ==
UserView.render("show.json", %{user: fetched_user})
data = %{
"nickname" => "GrimReaper",
"email" => "death@reapers.afterlife",
"fullname" => "Reaper Grim",
"bio" => "Your time has come",
"password" => "scythe",
"confirm" => "scythe",
"token" => invite.token
}
{:error, msg} = TwitterAPI.register_user(data)
assert msg == "Expired token"
refute User.get_by_nickname("GrimReaper")
end
end
describe "registers with reusable date limited token" do
setup do
setting = Pleroma.Config.get([:instance, :registrations_open])
if setting do
Pleroma.Config.put([:instance, :registrations_open], false)
on_exit(fn -> Pleroma.Config.put([:instance, :registrations_open], setting) end)
end
:ok
end
test "returns user on success" do
{:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.utc_today(), max_use: 100})
data = %{
"nickname" => "vinny",
"email" => "pasta@pizza.vs",
"fullname" => "Vinny Vinesauce",
"bio" => "streamer",
"password" => "hiptofbees",
"confirm" => "hiptofbees",
"token" => invite.token
}
{:ok, user} = TwitterAPI.register_user(data)
fetched_user = User.get_by_nickname("vinny")
invite = Repo.get_by(UserInviteToken, token: invite.token)
refute invite.used
assert UserView.render("show.json", %{user: user}) ==
UserView.render("show.json", %{user: fetched_user})
end
test "error after max uses" do
{:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.utc_today(), max_use: 100})
UserInviteToken.update_invite!(invite, uses: 99)
data = %{
"nickname" => "vinny",
"email" => "pasta@pizza.vs",
"fullname" => "Vinny Vinesauce",
"bio" => "streamer",
"password" => "hiptofbees",
"confirm" => "hiptofbees",
"token" => invite.token
}
{:ok, user} = TwitterAPI.register_user(data)
fetched_user = User.get_by_nickname("vinny")
invite = Repo.get_by(UserInviteToken, token: invite.token)
assert invite.used == true
assert UserView.render("show.json", %{user: user}) ==
UserView.render("show.json", %{user: fetched_user})
data = %{
"nickname" => "GrimReaper",
"email" => "death@reapers.afterlife",
"fullname" => "Reaper Grim",
"bio" => "Your time has come",
"password" => "scythe",
"confirm" => "scythe",
"token" => invite.token
}
{:error, msg} = TwitterAPI.register_user(data)
assert msg == "Expired token"
refute User.get_by_nickname("GrimReaper")
end
test "returns error on overdue date" do
{:ok, invite} =
UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), -1), max_use: 100})
data = %{
"nickname" => "GrimReaper",
"email" => "death@reapers.afterlife",
"fullname" => "Reaper Grim",
"bio" => "Your time has come",
"password" => "scythe",
"confirm" => "scythe",
"token" => invite.token
}
{:error, msg} = TwitterAPI.register_user(data)
assert msg == "Expired token"
refute User.get_by_nickname("GrimReaper")
end
test "returns error on with overdue date and after max" do
{:ok, invite} =
UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), -1), max_use: 100})
UserInviteToken.update_invite!(invite, uses: 100)
data = %{
"nickname" => "GrimReaper",
"email" => "death@reapers.afterlife",
"fullname" => "Reaper Grim",
"bio" => "Your time has come",
"password" => "scythe",
"confirm" => "scythe",
"token" => invite.token
}
{:error, msg} = TwitterAPI.register_user(data)
assert msg == "Expired token"
refute User.get_by_nickname("GrimReaper")
end
end
test "it returns the error on registration problems" do