-
-
Save josevalim/94bfd65e8eaf892ed700df349838796a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Proto.Keys.Key do | |
defstruct [:key, :protocol_version] | |
end | |
defmodule Foo do | |
use ExUnit.Case, register: false | |
defp respond_to_latest_org_request(_, _, _, _, _, _ \\ [], _ \\ []) do | |
:ok | |
end | |
test "org approval group enforcement" do | |
alice = TestClient.create_and_claim_user("alice@somewhere.com", "alice") | |
%{entity_id: entity_id, admin_initializer: admin_initializer, admin_group_id: admin_group_id} = TestClient.create_org(alice) | |
params = [ | |
%{user_id: "bob@somewhere.com", display_name: "bob", department: "foo"}, | |
%{user_id: "carlos@somewhere.com", display_name: "carlos", department: "bar"}, | |
%{user_id: "david@somewhere.com", display_name: "david"}, | |
%{user_id: "eliza@somewhere.com", display_name: "eliza"}, | |
%{user_id: "frank@somewhere.com", display_name: "frank"}, | |
%{user_id: "gabby@somewhere.com", display_name: "gabby"} | |
] | |
resp = TestClient.post("/users/orgs/#{entity_id}/members", %{users: params}, alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"errors" => []} = Poison.decode!(resp.resp_body) | |
{secret1, key_version1} = TestClient.receive_claim_email("bob@somewhere.com") | |
{secret2, key_version2} = TestClient.receive_claim_email("carlos@somewhere.com") | |
{secret3, key_version3} = TestClient.receive_claim_email("david@somewhere.com") | |
{secret4, key_version4} = TestClient.receive_claim_email("eliza@somewhere.com") | |
{secret5, key_version5} = TestClient.receive_claim_email("frank@somewhere.com") | |
{secret6, key_version6} = TestClient.receive_claim_email("gabby@somewhere.com") | |
bob = TestClient.claim_user("bob@somewhere.com", secret1, key_version1) | |
assert %{"entity_id" => ^entity_id, "entity_metadata" => %{"role" => "standard", "department" => "foo"}} = bob.claim_return | |
carlos = TestClient.claim_user("carlos@somewhere.com", secret2, key_version2) | |
assert %{"entity_id" => ^entity_id, "entity_metadata" => %{"role" => "standard", "department" => "bar"}} = carlos.claim_return | |
david = TestClient.claim_user("david@somewhere.com", secret3, key_version3) | |
assert %{"entity_id" => ^entity_id, "entity_metadata" => %{"role" => "standard", "department" => nil}} = david.claim_return | |
eliza = TestClient.claim_user("eliza@somewhere.com", secret4, key_version4) | |
assert %{"entity_id" => ^entity_id, "entity_metadata" => %{"role" => "standard", "department" => nil}} = eliza.claim_return | |
frank = TestClient.claim_user("frank@somewhere.com", secret5, key_version5) | |
assert %{"entity_id" => ^entity_id, "entity_metadata" => %{"role" => "standard", "department" => nil}} = frank.claim_return | |
gabby = TestClient.claim_user("gabby@somewhere.com", secret6, key_version6) | |
assert %{"entity_id" => ^entity_id, "entity_metadata" => %{"role" => "standard", "department" => nil}} = gabby.claim_return | |
# create more accounts to test delete pending | |
params = [ | |
%{user_id: "pending@somewhere.com", display_name: "pending"}, | |
%{user_id: "pending_too@somewhere.com", display_name: "pending_too"} | |
] | |
resp = TestClient.post("/users/orgs/#{entity_id}/members", %{users: params}, alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"errors" => []} = Poison.decode!(resp.resp_body) | |
# create AGs | |
%{group_id: group_id_1, version: version_1} = | |
TestClient.create_org_ag(entity_id, alice, "admin_approval_group", [{alice, true}, {carlos, false}, {david, false}], 1) | |
%{group_id: group_id_2, version: version_2} = | |
TestClient.create_org_ag(entity_id, alice, "other_approval_group", [{alice, false}, {david, true}, {eliza, true}], 0) | |
%{group_id: group_id_3, version: version_3} = | |
TestClient.create_org_ag(entity_id, alice, "export_group", [{alice, false}, {carlos, true}, {david, false}, {eliza, false}], 2) | |
%{group_id: group_id_4} = | |
TestClient.create_org_ag(entity_id, alice, "useless_approval_group", [{alice, true}, {bob, false}, {david, false}], 1) | |
# setting admin_approval_group with non-admin users will be denied | |
request_params = | |
%{user_id: alice.user_id, | |
type: "change_org_approval_group_role", | |
device_id: alice.device.id, | |
data: %{group_id: group_id_1, group_role: "admin_approval_group", version: version_1}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{ | |
signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 400 | |
# promote bob to admin | |
admin_changes_hash = Base.encode16(:crypto.hash(:sha256, admin_initializer["payload"]), case: :lower) | |
payload = Poison.encode!(%{ | |
"timestamp" => NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
"predecessor" => admin_changes_hash, | |
"actions" => [%{ | |
"action" => "add", | |
"user_id" => bob.user_id, | |
"key_version" => bob.key_version, | |
"verify_key" => Base.encode64(Proto.Keys.PublicUserKey.encode(%Proto.Keys.Key{ | |
protocol_version: bob.sign_pair.protocol_version, | |
key: bob.sign_pair.public | |
})) | |
}] | |
}) | |
admin_changes_hash = Base.encode16(:crypto.hash(:sha256, payload), case: :lower) | |
signature = Base.encode64(TestClient.sign_detached(payload, alice.sign_pair.secret)) | |
admin_changes = %{ | |
user_id: alice.user_id, | |
key_version: alice.key_version, | |
signature: signature, | |
payload: payload | |
} | |
group_key_params = %{ | |
sharee_user_id: bob.user_id, | |
group_id: admin_group_id, | |
user_key_version: bob.key_version, | |
group_key_version: 0, | |
signature: Base.encode64(TestClient.sign_detached("#{bob.user_id},#{bob.key_version},#{admin_group_id},0,#{Base.encode16(:crypto.hash(:sha256, "dummy"), case: :lower)}", alice.sign_pair.secret)), | |
wrapped_key: Base.encode64("dummy") | |
} | |
request_params = | |
%{user_id: alice.user_id, | |
type: "change_admin_status", | |
device_id: alice.device.id, | |
data: %{user_id: bob.user_id, | |
role: "admin", | |
department: "gonna be fired"}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request, | |
admin_changes: admin_changes, | |
grant_group_key: group_key_params | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 200, resp.resp_body | |
resp = TestClient.request_users([bob], alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"users" => [ | |
%{"user_id" => "bob@somewhere.com", "claimed" => true, "entity_id" => ^entity_id, "entity_metadata" => %{"role" => "admin", "department" => "gonna be fired"}} | |
]} = Poison.decode!(resp.resp_body) | |
# promote carlos to admin | |
payload = Poison.encode!(%{ | |
"timestamp" => NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
"predecessor" => admin_changes_hash, | |
"actions" => [%{ | |
"action" => "add", | |
"user_id" => carlos.user_id, | |
"key_version" => carlos.key_version, | |
"verify_key" => Base.encode64(Proto.Keys.PublicUserKey.encode(%Proto.Keys.Key{ | |
protocol_version: carlos.sign_pair.protocol_version, | |
key: carlos.sign_pair.public | |
})) | |
}] | |
}) | |
admin_changes_hash = Base.encode16(:crypto.hash(:sha256, payload), case: :lower) | |
signature = Base.encode64(TestClient.sign_detached(payload, alice.sign_pair.secret)) | |
admin_changes = %{ | |
user_id: alice.user_id, | |
key_version: alice.key_version, | |
signature: signature, | |
payload: payload | |
} | |
group_key_params = %{ | |
sharee_user_id: carlos.user_id, | |
group_id: admin_group_id, | |
user_key_version: carlos.key_version, | |
group_key_version: 0, | |
signature: Base.encode64(TestClient.sign_detached("#{carlos.user_id},#{carlos.key_version},#{admin_group_id},0,#{Base.encode16(:crypto.hash(:sha256, "dummy"), case: :lower)}", alice.sign_pair.secret)), | |
wrapped_key: Base.encode64("dummy") | |
} | |
request_params = | |
%{user_id: alice.user_id, | |
type: "change_admin_status", | |
device_id: alice.device.id, | |
data: %{user_id: carlos.user_id, | |
role: "admin", | |
department: "gonna be demoted"}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request, | |
admin_changes: admin_changes, | |
grant_group_key: group_key_params | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 200, resp.resp_body | |
# promote david to admin | |
payload = Poison.encode!(%{ | |
"timestamp" => NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
"predecessor" => admin_changes_hash, | |
"actions" => [%{ | |
"action" => "add", | |
"user_id" => david.user_id, | |
"key_version" => david.key_version, | |
"verify_key" => Base.encode64(Proto.Keys.PublicUserKey.encode(%Proto.Keys.Key{ | |
protocol_version: david.sign_pair.protocol_version, | |
key: david.sign_pair.public | |
})) | |
}] | |
}) | |
admin_changes_hash = Base.encode16(:crypto.hash(:sha256, payload), case: :lower) | |
signature = Base.encode64(TestClient.sign_detached(payload, alice.sign_pair.secret)) | |
admin_changes = %{ | |
user_id: alice.user_id, | |
key_version: alice.key_version, | |
signature: signature, | |
payload: payload | |
} | |
group_key_params = %{ | |
sharee_user_id: david.user_id, | |
group_id: admin_group_id, | |
user_key_version: david.key_version, | |
group_key_version: 0, | |
signature: Base.encode64(TestClient.sign_detached("#{david.user_id},#{david.key_version},#{admin_group_id},0,#{Base.encode16(:crypto.hash(:sha256, "dummy"), case: :lower)}", alice.sign_pair.secret)), | |
wrapped_key: Base.encode64("dummy") | |
} | |
request_params = | |
%{user_id: alice.user_id, | |
type: "change_admin_status", | |
device_id: alice.device.id, | |
data: %{user_id: david.user_id, | |
role: "admin", | |
department: "right hand man"}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request, | |
admin_changes: admin_changes, | |
grant_group_key: group_key_params | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 200, resp.resp_body | |
# promote gabby to admin | |
payload = Poison.encode!(%{ | |
"timestamp" => NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
"predecessor" => admin_changes_hash, | |
"actions" => [%{ | |
"action" => "add", | |
"user_id" => gabby.user_id, | |
"key_version" => gabby.key_version, | |
"verify_key" => Base.encode64(Proto.Keys.PublicUserKey.encode(%Proto.Keys.Key{ | |
protocol_version: gabby.sign_pair.protocol_version, | |
key: gabby.sign_pair.public | |
})) | |
}] | |
}) | |
admin_changes_hash = Base.encode16(:crypto.hash(:sha256, payload), case: :lower) | |
signature = Base.encode64(TestClient.sign_detached(payload, alice.sign_pair.secret)) | |
admin_changes = %{ | |
user_id: alice.user_id, | |
key_version: alice.key_version, | |
signature: signature, | |
payload: payload | |
} | |
group_key_params = %{ | |
sharee_user_id: gabby.user_id, | |
group_id: admin_group_id, | |
user_key_version: gabby.key_version, | |
group_key_version: 0, | |
signature: Base.encode64(TestClient.sign_detached("#{gabby.user_id},#{gabby.key_version},#{admin_group_id},0,#{Base.encode16(:crypto.hash(:sha256, "dummy"), case: :lower)}", alice.sign_pair.secret)), | |
wrapped_key: Base.encode64("dummy") | |
} | |
request_params = | |
%{user_id: alice.user_id, | |
type: "change_admin_status", | |
device_id: alice.device.id, | |
data: %{user_id: gabby.user_id, | |
role: "admin", | |
department: "to delete"}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request, | |
admin_changes: admin_changes, | |
grant_group_key: group_key_params | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 200, resp.resp_body | |
# promote eliza to admin | |
payload = Poison.encode!(%{ | |
"timestamp" => NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
"predecessor" => admin_changes_hash, | |
"actions" => [%{ | |
"action" => "add", | |
"user_id" => eliza.user_id, | |
"key_version" => eliza.key_version, | |
"verify_key" => Base.encode64(Proto.Keys.PublicUserKey.encode(%Proto.Keys.Key{ | |
protocol_version: eliza.sign_pair.protocol_version, | |
key: eliza.sign_pair.public | |
})) | |
}] | |
}) | |
admin_changes_hash = Base.encode16(:crypto.hash(:sha256, payload), case: :lower) | |
signature = Base.encode64(TestClient.sign_detached(payload, alice.sign_pair.secret)) | |
admin_changes = %{ | |
user_id: alice.user_id, | |
key_version: alice.key_version, | |
signature: signature, | |
payload: payload | |
} | |
group_key_params = %{ | |
sharee_user_id: eliza.user_id, | |
group_id: admin_group_id, | |
user_key_version: eliza.key_version, | |
group_key_version: 0, | |
signature: Base.encode64(TestClient.sign_detached("#{eliza.user_id},#{eliza.key_version},#{admin_group_id},0,#{Base.encode16(:crypto.hash(:sha256, "dummy"), case: :lower)}", alice.sign_pair.secret)), | |
wrapped_key: Base.encode64("dummy") | |
} | |
request_params = | |
%{user_id: alice.user_id, | |
type: "change_admin_status", | |
device_id: alice.device.id, | |
data: %{user_id: eliza.user_id, | |
role: "admin", | |
department: "to delete"}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request, | |
admin_changes: admin_changes, | |
grant_group_key: group_key_params | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 200, resp.resp_body | |
# can set AG on member right away | |
resp = TestClient.get("/users/orgs/#{entity_id}/groups", alice) | |
assert resp.status == 200, resp.resp_body | |
decoded_resp = Poison.decode!(resp.resp_body) | |
assert %{ | |
"name" => "other_approval_group", | |
"id" => ^group_id_2, | |
"version" => ^version_2, | |
"group" => %{ | |
"optionals_required" => 0, | |
"approvers" => g2_approvers | |
}, | |
"is_deleted" => false, | |
"rev_id" => _ | |
} = Enum.find(decoded_resp["groups"], fn x -> x["id"] == group_id_2 end) | |
TestClient.set_org_ag(alice, gabby, group_id_2, g2_approvers, 0) | |
# gabby has AG set | |
resp = TestClient.get("/users/approvers/info?#{URI.encode_query(user_id: gabby.user_id)}", gabby) | |
assert resp.status == 200, resp.resp_body | |
assert %{"optional_users" => [%{"user_id" => "alice@somewhere.com", "display_name" => "alice"}], | |
"required_users" => [_ | _], | |
"id" => ^group_id_2, | |
"optionals_required" => 0} = Poison.decode! resp.resp_body | |
# deleting a user should go through right away | |
payload = Poison.encode!(%{ | |
"timestamp" => NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
"predecessor" => admin_changes_hash, | |
"actions" => [%{ | |
"action" => "remove", | |
"user_id" => gabby.user_id, | |
"key_version" => gabby.key_version, | |
"verify_key" => Base.encode64(Proto.Keys.PublicUserKey.encode(%Proto.Keys.Key{ | |
protocol_version: gabby.sign_pair.protocol_version, | |
key: gabby.sign_pair.public | |
})) | |
}] | |
}) | |
admin_changes_hash = Base.encode16(:crypto.hash(:sha256, payload), case: :lower) | |
signature = Base.encode64(TestClient.sign_detached(payload, alice.sign_pair.secret)) | |
admin_changes = %{ | |
user_id: alice.user_id, | |
key_version: alice.key_version, | |
signature: signature, | |
payload: payload | |
} | |
request_params = | |
%{user_id: alice.user_id, | |
type: "delete_user", | |
device_id: alice.device.id, | |
data: %{user_id: gabby.user_id}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request, | |
admin_changes: admin_changes | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 200, resp.resp_body | |
# gabby no longer exists | |
resp = TestClient.request_users([gabby], alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"users" => [], "errors" => [e]} = Poison.decode!(resp.resp_body) | |
assert %{"title" => "missing-entity"} = e | |
# test deleting a pending user going through right away | |
# pending user exists | |
resp = TestClient.post("/users/find", %{spec: [%{user_id: "pending@somewhere.com"}]}, alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"users" => [%{"display_name" => "pending"}], "errors" => []} = Poison.decode!(resp.resp_body) | |
request_params = | |
%{user_id: alice.user_id, | |
type: "delete_user", | |
device_id: alice.device.id, | |
data: %{user_id: "pending@somewhere.com"}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 200, resp.resp_body | |
# pending user no longer exists | |
resp = TestClient.post("/users/find", %{spec: [%{user_id: "pending@somewhere.com"}]}, alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"users" => [], "errors" => [e]} = Poison.decode!(resp.resp_body) | |
assert %{"title" => "missing-entity"} = e | |
# setting the admin approval group is via the approval group API | |
request_params = | |
%{user_id: alice.user_id, | |
type: "change_org_approval_group_role", | |
device_id: alice.device.id, | |
data: %{group_id: group_id_1, group_role: "admin_approval_group", version: version_1}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{ | |
signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 200, resp.resp_body | |
resp = TestClient.get("/users/orgs/#{entity_id}", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"roled_approval_groups" => %{"admin_approval_group" => %{"group_id" => ^group_id_1, "version" => ^version_1}}} = Poison.decode!(resp.resp_body) | |
# cannot delete a group in use as a member's AG | |
resp = TestClient.delete("/users/orgs/#{entity_id}/groups/#{group_id_1}", alice) | |
assert resp.status == 403, resp.resp_body | |
# keep track of amount of requests made - for testing paging of requests | |
org_requests_count = 0 | |
# test changing the admin approval group | |
# first, giving an invalid UUID as new admin group id should fail right away | |
request_params = | |
%{user_id: alice.user_id, | |
type: "change_org_approval_group_role", | |
device_id: alice.device.id, | |
data: %{group_id: CollectionServer.Types.UUID.generate, group_role: "admin_approval_group", version: version_2}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 400, resp.resp_body | |
# Alice makes request to change admin approval group | |
request_params = | |
%{user_id: alice.user_id, | |
type: "change_org_approval_group_role", | |
device_id: alice.device.id, | |
data: %{group_id: group_id_2, group_role: "admin_approval_group", version: version_2}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"request" => %{"request_id" => _request_id, "type" => "change_org_approval_group_role"}} = Poison.decode! resp.resp_body | |
org_requests_count = org_requests_count + 1 | |
# the org admin approval group still the first; no changes made | |
resp = TestClient.get("/users/orgs/#{entity_id}", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"roled_approval_groups" => %{"admin_approval_group" => %{"group_id" => ^group_id_1, "version" => ^version_1}}} = Poison.decode!(resp.resp_body) | |
# Alice fetches her requests | |
resp = TestClient.get("/users/orgs/#{entity_id}/requests", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"requests" => [%{"type" => "change_org_approval_group_role", "status" => "pending"}]} = Poison.decode!(resp.resp_body) | |
# Bob can see the request too, as an admin | |
resp = TestClient.get("/users/orgs/#{entity_id}/requests", bob) | |
assert resp.status == 200, resp.resp_body | |
assert %{"requests" => [%{"type" => "change_org_approval_group_role", "status" => "pending"}]} = Poison.decode!(resp.resp_body) | |
# alice should see that she has automatically approved her own request | |
resp = TestClient.get("/users/approvals?#{URI.encode_query(user_id: alice.user_id)}", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"approvals" => [%{"request_id" => request_id, | |
"payload" => payload, | |
"status" => "pending", | |
"response" => "approved"}]} = Poison.decode! resp.resp_body | |
# Alice shouldn't be able to change her response or resubmit it. | |
params = %{requester_user_id: alice.user_id, | |
signature: Base.encode64(TestClient.sign_detached(payload, alice.sign_pair.secret)), | |
approve: false} | |
resp = TestClient.put("/users/orgs/#{entity_id}/requests/#{request_id}", params, alice) | |
assert resp.status == 409, resp.resp_body | |
params = %{requester_user_id: alice.user_id, | |
signature: Base.encode64(TestClient.sign_detached(payload, alice.sign_pair.secret)), | |
approve: true} | |
resp = TestClient.put("/users/orgs/#{entity_id}/requests/#{request_id}", params, alice) | |
assert resp.status == 409, resp.resp_body | |
# alice can check status of the request | |
resp = TestClient.get("/users/orgs/#{entity_id}/requests", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"requests" => [%{"type" => "change_org_approval_group_role", "status" => "pending", "request_id" => request_id}]} = Poison.decode!(resp.resp_body) | |
resp = TestClient.get("/users/orgs/#{entity_id}/requests/#{request_id}/responses", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"responses" => r} = Poison.decode!(resp.resp_body) | |
assert Enum.count(r) == 3 | |
# carlos fetches and denies her request | |
respond_to_latest_org_request(carlos, entity_id, request_id, alice.user_id, false) | |
# david fetches and approves her request | |
respond_to_latest_org_request(david, entity_id, request_id, alice.user_id, true) | |
# alice should see the new admin approver group | |
resp = TestClient.get("/users/orgs/#{entity_id}", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"roled_approval_groups" => %{"admin_approval_group" => %{"group_id" => ^group_id_2, "version" => ^version_2}}} = Poison.decode!(resp.resp_body) | |
# alice can check status of the request | |
resp = TestClient.get("/users/orgs/#{entity_id}/requests", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"requests" => [%{"type" => "change_org_approval_group_role", "status" => "approved"}]} = Poison.decode!(resp.resp_body) | |
# david should see the request as no longer pending | |
resp = TestClient.get("/users/approvals?#{URI.encode_query(user_id: david.user_id)}", david) | |
assert resp.status == 200, resp.resp_body | |
assert %{"approvals" => [%{"status" => "approved", "response" => "approved"}]} = Poison.decode! resp.resp_body | |
# keep track of amount of approvals by the new admin group - for testing paging of approvals | |
approvals_count = 0 | |
# test request setting AG for members | |
# david has empty AG | |
resp = TestClient.get("/users/approvers/info?#{URI.encode_query(user_id: david.user_id)}", david) | |
assert resp.status == 200, resp.resp_body | |
assert %{"id" => _, | |
"optional_users" => [], | |
"required_users" => [], | |
"optionals_required" => 0} = Poison.decode! resp.resp_body | |
# alice makes request to set davids's and carlos' AG to group_1 | |
# TODO: currently don't default to admin AG | |
resp = TestClient.get("/users/orgs/#{entity_id}/groups", alice) | |
assert resp.status == 200, resp.resp_body | |
decoded_resp = Poison.decode!(resp.resp_body) | |
assert %{ | |
"name" => "admin_approval_group", | |
"id" => ^group_id_1, | |
"version" => ^version_1, | |
"group" => %{ | |
"optionals_required" => 1, | |
"approvers" => g1_approvers | |
}, | |
"is_deleted" => false, | |
"rev_id" => _ | |
} = Enum.find(decoded_resp["groups"], fn x -> x["id"] == group_id_1 end) | |
event_payload = %{ | |
type: :set_approval_group, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
for_user_id: david.user_id, | |
data: %{ | |
id: group_id_1, | |
optionals_required: 1, | |
approvers: g1_approvers | |
} | |
} | |
david_event_payload = Poison.encode!(event_payload) | |
david_signature = Base.encode64(TestClient.sign_detached(david_event_payload, alice.sign_pair.secret)) | |
carlos_event_payload = Poison.encode!(%{event_payload | for_user_id: carlos.user_id}) | |
carlos_signature = Base.encode64(TestClient.sign_detached(carlos_event_payload, alice.sign_pair.secret)) | |
request_params = | |
%{user_id: alice.user_id, | |
type: "set_member_approval_group", | |
device_id: alice.device.id, | |
data: %{current_group_id: nil, | |
current_group_version: nil, | |
requester_key_version: alice.key_version, | |
events: [ | |
%{user_id: david.user_id, | |
signature: david_signature, | |
payload: david_event_payload | |
}, | |
%{user_id: carlos.user_id, | |
signature: carlos_signature, | |
payload: carlos_event_payload | |
} | |
]}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 200, resp.resp_body | |
# carlos gets empty AG, still | |
resp = TestClient.get("/users/approvers/info?#{URI.encode_query(user_id: carlos.user_id)}", carlos) | |
assert resp.status == 200, resp.resp_body | |
assert %{"id" => _, | |
"optional_users" => [], | |
"required_users" => [], | |
"optionals_required" => 0} = Poison.decode! resp.resp_body | |
# carlos should now have an event | |
resp = TestClient.get("/users/events?#{URI.encode_query(user_id: carlos.user_id)}", carlos) | |
assert resp.status == 200, resp.resp_body | |
assert %{"last_rev_id" => _, "events" => [%{ | |
"id" => event_id, | |
"user_id" => "alice@somewhere.com", | |
"key_version" => n, | |
"signature" => ^carlos_signature, | |
"payload" => ^carlos_event_payload, | |
"handled" => false, | |
"rev_id" => _ | |
}]} = Poison.decode!(resp.resp_body) | |
assert n == alice.key_version | |
assert TestClient.verify_detached(Base.decode64!(carlos_signature), carlos_event_payload, alice.sign_pair.public) == :valid | |
request = %{ | |
approvers: [ | |
%{ | |
user_id: alice.user_id, | |
secret: "whatever", | |
key_version: alice.key_version, | |
account_version: 0, | |
required: true, | |
protocol_version: 2 | |
}, | |
%{ | |
user_id: carlos.user_id, | |
secret: "something", | |
key_version: carlos.key_version, | |
account_version: 0, | |
required: false, | |
protocol_version: 2 | |
}, | |
%{ | |
user_id: david.user_id, | |
secret: "nothing", | |
key_version: david.key_version, | |
account_version: 0, | |
required: false, | |
protocol_version: 2 | |
} | |
], | |
optionals_required: 1 | |
} | |
resp = TestClient.put("/users/events/#{event_id}", %{user_id: carlos.user_id, request: request}, carlos) | |
assert resp.status == 200, resp.resp_body | |
# carlos should have an ag set | |
resp = TestClient.get("/users/approvers/info?#{URI.encode_query(user_id: carlos.user_id)}", carlos) | |
assert resp.status == 200, resp.resp_body | |
assert %{"optional_users" => _, | |
"required_users" => _, | |
"id" => ^group_id_1, | |
"optionals_required" => 1} = Poison.decode! resp.resp_body | |
# test changing export group | |
event_payload = Poison.encode!(%{ | |
type: :submit_shards_to_export_group, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
for_user_id: nil, | |
data: %{group_id: group_id_3, group_version: version_3} | |
}) | |
signature = Base.encode64(TestClient.sign_detached(event_payload, alice.sign_pair.secret)) | |
event_params = %{ | |
user_id: nil, # all members of organization | |
requester_id: alice.user_id, | |
requester_key_version: alice.key_version, | |
signature: signature, | |
payload: event_payload | |
} | |
request_params = | |
%{user_id: alice.user_id, | |
type: "change_org_approval_group_role", | |
device_id: alice.device.id, | |
data: %{group_id: group_id_3, group_role: "export_approval_group", version: version_3, event: event_params}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{ | |
signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"request" => %{"request_id" => request_id, "type" => "change_org_approval_group_role"}} = Poison.decode! resp.resp_body | |
org_requests_count = org_requests_count + 1 | |
approvals_count = approvals_count + 1 | |
resp = TestClient.get("/users/orgs/#{entity_id}", alice) | |
assert resp.status == 200, resp.resp_body | |
%{"roled_approval_groups" => ags} = Poison.decode!(resp.resp_body) | |
assert ags["export_approval_group"] == nil | |
# since no export AG set, the admin group must allow the change | |
# david fetches and approves her request | |
respond_to_latest_org_request(david, entity_id, request_id, alice.user_id, true) | |
# eliza fetches and approves her request | |
respond_to_latest_org_request(eliza, entity_id, request_id, alice.user_id, true) | |
# alice conforms change | |
resp = TestClient.get("/users/orgs/#{entity_id}", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"roled_approval_groups" => %{"admin_approval_group" => %{"group_id" => ^group_id_2, "version" => ^version_2}, | |
"export_approval_group" => %{"group_id" => ^group_id_3, "version" => ^version_3}}} = Poison.decode!(resp.resp_body) | |
# users should've gotten an event | |
resp = TestClient.get("/users/events?#{URI.encode_query(user_id: carlos.user_id)}", carlos) | |
assert resp.status == 200 | |
assert Enum.count(Poison.decode!(resp.resp_body)["events"]) == 2 | |
# now changing the export group requires old export group's approval | |
event_payload = Poison.encode!(%{ | |
type: :submit_shards_to_export_group, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
for_user_id: nil, | |
data: %{group_id: group_id_1, group_version: version_1} | |
}) | |
signature = Base.encode64(TestClient.sign_detached(event_payload, alice.sign_pair.secret)) | |
event_params = %{ | |
user_id: nil, # all members of organization | |
requester_id: alice.user_id, | |
requester_key_version: alice.key_version, | |
signature: signature, | |
payload: event_payload | |
} | |
request_params = | |
%{user_id: alice.user_id, | |
type: "change_org_approval_group_role", | |
device_id: alice.device.id, | |
data: %{group_id: group_id_1, group_role: "export_approval_group", version: version_1, event: event_params}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{ | |
signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"request" => %{"request_id" => _request_id, "type" => "change_org_approval_group_role"}} = Poison.decode! resp.resp_body | |
org_requests_count = org_requests_count + 1 | |
approvals_count = approvals_count + 1 | |
# alice should have request already approved | |
resp = TestClient.get("/users/approvals?#{URI.encode_query(user_id: alice.user_id)}", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"approvals" => [%{"request_id" => request_id, | |
"payload" => _payload, | |
"status" => "pending", | |
"response" => "approved"} | _]} = Poison.decode!(resp.resp_body) | |
# carlos fetches and approves her request | |
respond_to_latest_org_request(carlos, entity_id, request_id, alice.user_id, true) | |
# ensure no change to the group yet | |
resp = TestClient.get("/users/orgs/#{entity_id}", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"roled_approval_groups" => %{"admin_approval_group" => %{"group_id" => ^group_id_2, "version" => ^version_2}, | |
"export_approval_group" => %{"group_id" => ^group_id_3, "version" => ^version_3}}} = Poison.decode!(resp.resp_body) | |
# eliza fetches and approves her request | |
respond_to_latest_org_request(eliza, entity_id, request_id, alice.user_id, true) | |
resp = TestClient.get("/users/orgs/#{entity_id}", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"roled_approval_groups" => %{"admin_approval_group" => %{"group_id" => ^group_id_2, "version" => ^version_2}, | |
"export_approval_group" => %{"group_id" => ^group_id_1, "version" => ^version_1}}} = Poison.decode!(resp.resp_body) | |
# users should've gotten an event | |
resp = TestClient.get("/users/events?#{URI.encode_query(user_id: carlos.user_id)}", carlos) | |
assert resp.status == 200 | |
assert Enum.count(Poison.decode!(resp.resp_body)["events"]) == 3 | |
# alice makes request for carlos to rekey and set AG to group 2 | |
resp = TestClient.get("/users/orgs/#{entity_id}/groups", alice) | |
assert resp.status == 200, resp.resp_body | |
decoded_resp = Poison.decode!(resp.resp_body) | |
assert %{ | |
"name" => "other_approval_group", | |
"id" => ^group_id_2, | |
"version" => ^version_2, | |
"group" => %{ | |
"optionals_required" => 0, | |
"approvers" => g2_approvers | |
}, | |
"is_deleted" => false, | |
"rev_id" => _ | |
} = Enum.find(decoded_resp["groups"], fn x -> x["id"] == group_id_2 end) | |
carlos_event_payload = Poison.encode!(%{ | |
type: :rekey_and_set_approval_group, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
for_user_id: carlos.user_id, | |
data: %{ | |
id: group_id_2, | |
version: version_2, | |
optionals_required: 0, | |
approvers: g2_approvers | |
} | |
}) | |
carlos_signature = Base.encode64(TestClient.sign_detached(carlos_event_payload, alice.sign_pair.secret)) | |
# Shouldn't work without providing group info | |
request_params = | |
%{user_id: alice.user_id, | |
type: "member_rekey_and_set_approval_group", | |
device_id: alice.device.id, | |
data: %{current_group_id: nil, | |
current_group_version: nil, | |
requester_key_version: alice.key_version, | |
events: [ | |
%{user_id: carlos.user_id, | |
signature: carlos_signature, | |
payload: carlos_event_payload | |
} | |
]}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 400, resp.resp_body | |
# works when we set the group id and version | |
request_params = | |
%{user_id: alice.user_id, | |
type: "member_rekey_and_set_approval_group", | |
device_id: alice.device.id, | |
data: %{ | |
current_group_id: group_id_1, | |
current_group_version: version_1, | |
requester_key_version: alice.key_version, | |
events: [ | |
%{user_id: carlos.user_id, | |
signature: carlos_signature, | |
payload: carlos_event_payload | |
} | |
]}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"request" => %{"request_id" => request_id, "type" => "member_rekey_and_set_approval_group"}} = Poison.decode! resp.resp_body | |
org_requests_count = org_requests_count + 1 | |
# not approved by the admin AG, so do no raise that count for eliza | |
# carlos still has old AG | |
resp = TestClient.get("/users/approvers/info?#{URI.encode_query(user_id: carlos.user_id)}", carlos) | |
assert resp.status == 200, resp.resp_body | |
assert %{"id" => ^group_id_1, "optionals_required" => 1} = Poison.decode! resp.resp_body | |
# carlos doesn't have a new event yet | |
resp = TestClient.get("/users/events?#{URI.encode_query(user_id: carlos.user_id)}", carlos) | |
assert resp.status == 200 | |
assert Enum.count(Poison.decode!(resp.resp_body)["events"]) == 3 | |
# alice sees her response as already-approved | |
resp = TestClient.get("/users/approvals?#{URI.encode_query(user_id: alice.user_id)}", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"approvals" => [%{"request_id" => ^request_id, | |
"payload" => _payload, | |
"status" => "pending", | |
"response" => "approved"} | _]} = Poison.decode!(resp.resp_body) | |
# david fetches and approves her request | |
respond_to_latest_org_request(david, entity_id, request_id, alice.user_id, true) | |
# carlos should now have an event | |
resp = TestClient.get("/users/events?#{URI.encode_query(user_id: carlos.user_id)}", carlos) | |
assert resp.status == 200, resp.resp_body | |
assert %{"last_rev_id" => _, "events" => [%{ | |
"id" => event_id, | |
"user_id" => "alice@somewhere.com", | |
"key_version" => n, | |
"signature" => ^carlos_signature, | |
"payload" => ^carlos_event_payload, | |
"handled" => false, | |
"rev_id" => _ | |
} | _]} = Poison.decode!(resp.resp_body) | |
assert n == alice.key_version | |
assert TestClient.verify_detached(Base.decode64!(carlos_signature), carlos_event_payload, alice.sign_pair.public) == :valid | |
# as an admin, carlos creates new key, makes admin changes struct, shares group key with new key, and shards key for his new AG and current export group | |
{sign_pair, _, public_key, public_key_proto} = TestClient.generate_keys(1) | |
payload = Poison.encode!(%{ | |
"timestamp" => NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
"predecessor" => admin_changes_hash, | |
"actions" => [%{ | |
"action" => "add", | |
"user_id" => carlos.user_id, | |
"key_version" => 1, | |
"verify_key" => Base.encode64(Proto.Keys.PublicUserKey.encode(%Proto.Keys.Key{ | |
protocol_version: 1, | |
key: sign_pair.public | |
})) | |
}] | |
}) | |
admin_changes_hash = Base.encode16(:crypto.hash(:sha256, payload), case: :lower) | |
signature = Base.encode64(TestClient.sign_detached(payload, carlos.sign_pair.secret)) | |
admin_changes = %{ | |
user_id: carlos.user_id, | |
key_version: carlos.key_version, | |
signature: signature, | |
payload: payload | |
} | |
grant_group_key_params = %{ | |
sharee_user_id: carlos.user_id, | |
group_id: admin_group_id, | |
user_key_version: carlos.key_version, | |
group_key_version: 0, | |
signature: Base.encode64(TestClient.sign_detached("#{carlos.user_id},#{carlos.key_version},#{admin_group_id},0,#{Base.encode16(:crypto.hash(:sha256, "dummy"), case: :lower)}", carlos.sign_pair.secret)), | |
wrapped_key: Base.encode64("dummy") | |
} | |
request = %{ | |
approvers: [ | |
%{ | |
user_id: "alice@somewhere.com", | |
secret: "whatever", | |
key_version: alice.key_version, | |
account_version: 0, | |
required: false, | |
protocol_version: 2 | |
}, | |
%{ | |
user_id: "david@somewhere.com", | |
secret: "anything", | |
key_version: david.key_version, | |
account_version: 0, | |
required: true, | |
protocol_version: 2 | |
}, | |
%{ | |
user_id: "eliza@somewhere.com", | |
secret: "something", | |
key_version: eliza.key_version, | |
account_version: 0, | |
required: true, | |
protocol_version: 2 | |
} | |
], | |
optionals_required: 0, | |
public_key: public_key_proto, | |
wrapped_last_key: Base.encode64("foo"), | |
admin_changes: admin_changes, | |
grant_group_key: grant_group_key_params, | |
export_group_id: group_id_1, | |
export_group_version: version_1, | |
export_approvers: [ | |
%{user_id: alice.user_id, | |
key_version: 0, | |
secret: Base.encode64("thing 1"), | |
wrapped_key_version: 0, | |
sharder_key_version: 0, | |
protocol_version: 2 | |
}, | |
%{user_id: carlos.user_id, | |
key_version: 0, | |
secret: Base.encode64("do not lose this"), | |
wrapped_key_version: 0, | |
sharder_key_version: 0, | |
protocol_version: 2 | |
}, | |
%{user_id: david.user_id, | |
key_version: 0, | |
secret: Base.encode64("real shard for sure"), | |
wrapped_key_version: 0, | |
sharder_key_version: 0, | |
protocol_version: 2 | |
} | |
] | |
} | |
resp = TestClient.put("/users/events/#{event_id}", %{user_id: carlos.user_id, request: request}, carlos) | |
assert resp.status == 200, resp.resp_body | |
# carlos should not be able to use old key | |
resp = TestClient.get("/users/approvers/info?#{URI.encode_query(user_id: carlos.user_id)}", carlos) | |
assert resp.status == 498, resp.resp_body | |
# carlos should now be able to use the new key | |
carlos = %{carlos | sign_pair: sign_pair, public_key: public_key, key_version: 1} | |
# carlos should have ag set to 2 | |
resp = TestClient.get("/users/approvers/info?#{URI.encode_query(user_id: carlos.user_id)}", carlos) | |
assert resp.status == 200, resp.resp_body | |
assert %{"optional_users" => [%{"user_id" => "alice@somewhere.com", "display_name" => "alice"}], | |
"required_users" => [_ | _], | |
"id" => ^group_id_2, | |
"optionals_required" => 0} = Poison.decode! resp.resp_body | |
# request admin change | |
resp = TestClient.request_users([carlos], alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"users" => [ | |
%{"user_id" => "carlos@somewhere.com", "claimed" => true, "entity_id" => ^entity_id, "entity_metadata" => %{"role" => "admin", "department" => "gonna be demoted"}} | |
]} = Poison.decode!(resp.resp_body) | |
# alice makes request to demote carlos | |
admin_changes_payload = Poison.encode!(%{ | |
"timestamp" => NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
"predecessor" => admin_changes_hash, | |
"actions" => [%{ | |
"action" => "remove", | |
"user_id" => carlos.user_id, | |
"key_version" => carlos.key_version, | |
"verify_key" => Base.encode64(Proto.Keys.PublicUserKey.encode(%Proto.Keys.Key{ | |
protocol_version: 1, | |
key: carlos.sign_pair.public | |
})) | |
}] | |
}) | |
admin_changes_hash = Base.encode16(:crypto.hash(:sha256, admin_changes_payload), case: :lower) | |
signature = Base.encode64(TestClient.sign_detached(admin_changes_payload, alice.sign_pair.secret)) | |
admin_changes = %{ | |
user_id: alice.user_id, | |
key_version: alice.key_version, | |
signature: signature, | |
payload: admin_changes_payload | |
} | |
request_params = | |
%{user_id: alice.user_id, | |
type: "change_admin_status", | |
device_id: alice.device.id, | |
data: %{user_id: carlos.user_id, | |
role: "standard", | |
department: "intern"}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request, | |
admin_changes: admin_changes | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"request" => %{"request_id" => request_id, "type" => "change_admin_status"}} = Poison.decode! resp.resp_body | |
org_requests_count = org_requests_count + 1 | |
approvals_count = approvals_count + 1 | |
# carlos still admin | |
resp = TestClient.request_users([carlos], alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"users" => [ | |
%{"user_id" => "carlos@somewhere.com", "claimed" => true, "entity_id" => ^entity_id, "entity_metadata" => %{"role" => "admin", "department" => "gonna be demoted"}} | |
]} = Poison.decode!(resp.resp_body) | |
# david fetches and approves her request | |
signature = Base.encode64(TestClient.sign_detached(admin_changes_payload, david.sign_pair.secret)) | |
admin_changes = %{ | |
user_id: david.user_id, | |
key_version: david.key_version, | |
signature: signature, | |
payload: admin_changes_payload | |
} | |
respond_to_latest_org_request(david, entity_id, request_id, alice.user_id, true, admin_changes) | |
# eliza fetches and approves her request | |
signature = Base.encode64(TestClient.sign_detached(admin_changes_payload, eliza.sign_pair.secret)) | |
admin_changes = %{ | |
user_id: eliza.user_id, | |
key_version: eliza.key_version, | |
signature: signature, | |
payload: admin_changes_payload | |
} | |
respond_to_latest_org_request(eliza, entity_id, request_id, alice.user_id, true, admin_changes) | |
# carlos no longer admin | |
resp = TestClient.request_users([carlos], alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"users" => [ | |
%{"user_id" => "carlos@somewhere.com", "claimed" => true, "entity_id" => ^entity_id, "entity_metadata" => %{"role" => "standard", "department" => "intern"}} | |
]} = Poison.decode!(resp.resp_body) | |
# cannot request to demote admin AG member | |
admin_changes_payload = Poison.encode!(%{ | |
"timestamp" => NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
"predecessor" => admin_changes_hash, | |
"actions" => [%{ | |
"action" => "remove", | |
"user_id" => eliza.user_id, | |
"key_version" => eliza.key_version, | |
"verify_key" => Base.encode64(Proto.Keys.PublicUserKey.encode(%Proto.Keys.Key{ | |
protocol_version: 1, | |
key: eliza.sign_pair.public | |
})) | |
}] | |
}) | |
signature = Base.encode64(TestClient.sign_detached(admin_changes_payload, alice.sign_pair.secret)) | |
admin_changes = %{ | |
user_id: alice.user_id, | |
key_version: alice.key_version, | |
signature: signature, | |
payload: admin_changes_payload | |
} | |
request_params = | |
%{user_id: alice.user_id, | |
type: "change_admin_status", | |
device_id: alice.device.id, | |
data: %{user_id: eliza.user_id, | |
role: "standard", | |
department: "intern"}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request, | |
admin_changes: admin_changes | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 400, resp.resp_body | |
# test deleting an org admin (requires admin changes) | |
admin_changes_payload = Poison.encode!(%{ | |
"timestamp" => NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
"predecessor" => admin_changes_hash, | |
"actions" => [%{ | |
"action" => "remove", | |
"user_id" => bob.user_id, | |
"key_version" => bob.key_version, | |
"verify_key" => Base.encode64(Proto.Keys.PublicUserKey.encode(%Proto.Keys.Key{ | |
protocol_version: 1, | |
key: bob.sign_pair.public | |
})) | |
}] | |
}) | |
admin_changes_hash = Base.encode16(:crypto.hash(:sha256, admin_changes_payload), case: :lower) | |
signature = Base.encode64(TestClient.sign_detached(admin_changes_payload, alice.sign_pair.secret)) | |
admin_changes = %{ | |
user_id: alice.user_id, | |
key_version: alice.key_version, | |
signature: signature, | |
payload: admin_changes_payload | |
} | |
request_params = | |
%{user_id: alice.user_id, | |
type: "delete_user", | |
device_id: alice.device.id, | |
data: %{user_id: bob.user_id}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request, | |
admin_changes: admin_changes | |
} | |
# cannot make the request since bob is in an AG | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 400, resp.resp_body | |
# deleting the AG allows request to go through | |
resp = TestClient.delete("/users/orgs/#{entity_id}/groups/#{group_id_4}", alice) | |
assert resp.status == 200, resp.resp_body | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"request" => %{"request_id" => request_id, "type" => "delete_user"}} = Poison.decode! resp.resp_body | |
org_requests_count = org_requests_count + 1 | |
approvals_count = approvals_count + 1 | |
# bob still exists | |
resp = TestClient.request_users([bob], alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"users" => [%{"display_name" => "bob"}], "errors" => []} = Poison.decode!(resp.resp_body) | |
# david fetches and approves her request | |
admin_changes = %{ | |
user_id: david.user_id, | |
key_version: david.key_version, | |
signature: Base.encode64(TestClient.sign_detached(admin_changes_payload, david.sign_pair.secret)), | |
payload: admin_changes_payload | |
} | |
respond_to_latest_org_request(david, entity_id, request_id, alice.user_id, true, admin_changes) | |
# eliza fetches and approves her request | |
# a new AG made before bob got deleted needs to be removed for the deletion to go through. | |
%{group_id: group_id_5} = TestClient.create_org_ag(entity_id, alice, "random_group", [{alice, false}, {bob, false}, {eliza, false}], 2) | |
admin_changes = %{ | |
user_id: eliza.user_id, | |
key_version: eliza.key_version, | |
signature: Base.encode64(TestClient.sign_detached(admin_changes_payload, eliza.sign_pair.secret)), | |
payload: admin_changes_payload | |
} | |
respond_to_latest_org_request(eliza, entity_id, request_id, alice.user_id, true, admin_changes, 409) | |
# bob still exists | |
resp = TestClient.request_users([bob], alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"users" => [%{"display_name" => "bob"}], "errors" => []} = Poison.decode!(resp.resp_body) | |
# delete the new AG to allow eliza's approval to go through | |
resp = TestClient.delete("/users/orgs/#{entity_id}/groups/#{group_id_5}", alice) | |
assert resp.status == 200, resp.resp_body | |
# now eliza's approval is good | |
respond_to_latest_org_request(eliza, entity_id, request_id, alice.user_id, true, admin_changes) | |
# bob no longer exists | |
resp = TestClient.request_users([bob], alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"users" => [], "errors" => [e]} = Poison.decode!(resp.resp_body) | |
assert %{"title" => "missing-entity"} = e | |
# cannot request to delete an admin AG member | |
admin_changes_payload = Poison.encode!(%{ | |
"timestamp" => NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
"predecessor" => admin_changes_hash, | |
"actions" => [%{ | |
"action" => "remove", | |
"user_id" => david.user_id, | |
"key_version" => david.key_version, | |
"verify_key" => Base.encode64(Proto.Keys.PublicUserKey.encode(%Proto.Keys.Key{ | |
protocol_version: 1, | |
key: david.sign_pair.public | |
})) | |
}] | |
}) | |
signature = Base.encode64(TestClient.sign_detached(admin_changes_payload, alice.sign_pair.secret)) | |
admin_changes = %{ | |
user_id: alice.user_id, | |
key_version: alice.key_version, | |
signature: signature, | |
payload: admin_changes_payload | |
} | |
request_params = | |
%{user_id: alice.user_id, | |
type: "delete_user", | |
device_id: alice.device.id, | |
data: %{user_id: david.user_id}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request, | |
admin_changes: admin_changes | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 400, resp.resp_body | |
# test deleting a pending user with AG enforcement | |
request_params = | |
%{user_id: alice.user_id, | |
type: "delete_user", | |
device_id: alice.device.id, | |
data: %{user_id: "pending_too@somewhere.com"}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request, | |
admin_changes: admin_changes | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"request" => %{"request_id" => request_id, "type" => "delete_user"}} = Poison.decode! resp.resp_body | |
org_requests_count = org_requests_count + 1 | |
approvals_count = approvals_count + 1 | |
# david and eliza approve | |
respond_to_latest_org_request(david, entity_id, request_id, alice.user_id, true) | |
respond_to_latest_org_request(eliza, entity_id, request_id, alice.user_id, true) | |
# test deleting normal user, without admin changes. | |
# This request is expected to be the last one and will be deleted below for testing. | |
request_params = | |
%{user_id: alice.user_id, | |
type: "delete_user", | |
device_id: alice.device.id, | |
data: %{user_id: frank.user_id}, | |
timestamp: NaiveDateTime.to_iso8601(NaiveDateTime.utc_now), | |
expiration: NaiveDateTime.to_iso8601(NaiveDateTime.add(NaiveDateTime.utc_now, 900)), | |
protocol_version: 1} | |
encoded_request = Poison.encode!(request_params) | |
params = | |
%{signature: Base.encode64(TestClient.sign_detached(encoded_request, alice.sign_pair.secret)), | |
request_payload: encoded_request | |
} | |
resp = TestClient.post("/users/requests", params, alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"request" => %{"request_id" => request_id, "type" => "delete_user"}} = Poison.decode! resp.resp_body | |
org_requests_count = org_requests_count + 1 | |
approvals_count = approvals_count + 1 | |
# frank still exists | |
resp = TestClient.request_users([frank], alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"users" => [%{"display_name" => "frank"}], "errors" => []} = Poison.decode!(resp.resp_body) | |
# david and eliza approve her request | |
respond_to_latest_org_request(david, entity_id, request_id, alice.user_id, true) | |
respond_to_latest_org_request(eliza, entity_id, request_id, alice.user_id, true) | |
# frank no longer exists | |
resp = TestClient.request_users([frank], alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"users" => [], "errors" => [e]} = Poison.decode!(resp.resp_body) | |
assert %{"title" => "missing-entity"} = e | |
# test paging requests | |
resp = TestClient.get("/users/orgs/#{entity_id}/requests", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"requests" => x, "total_rows" => ^org_requests_count} = Poison.decode!(resp.resp_body) | |
assert Enum.count(x) == org_requests_count | |
resp = TestClient.get("/users/orgs/#{entity_id}/requests?#{URI.encode_query(limit: 3)}", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"requests" => x, "total_rows" => ^org_requests_count} = Poison.decode!(resp.resp_body) | |
assert Enum.count(x) == 3 | |
resp = TestClient.get("/users/orgs/#{entity_id}/requests?#{URI.encode_query(limit: 5, offset: 0)}", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"requests" => x, "total_rows" => ^org_requests_count} = Poison.decode!(resp.resp_body) | |
assert Enum.count(x) == 5 | |
resp = TestClient.get("/users/orgs/#{entity_id}/requests?#{URI.encode_query(limit: 50, offset: 5)}", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"requests" => x, "total_rows" => ^org_requests_count} = Poison.decode!(resp.resp_body) | |
assert Enum.count(x) == org_requests_count - 5 | |
# test paging approvals | |
resp = TestClient.get("/users/approvals?#{URI.encode_query(user_id: eliza.user_id)}", eliza) | |
assert resp.status == 200, resp.resp_body | |
assert %{"approvals" => x, "total_rows" => ^approvals_count} = Poison.decode!(resp.resp_body) | |
assert Enum.count(x) == approvals_count | |
resp = TestClient.get("/users/approvals?#{URI.encode_query(user_id: eliza.user_id, limit: 3)}", eliza) | |
assert resp.status == 200, resp.resp_body | |
assert %{"approvals" => x, "total_rows" => ^approvals_count} = Poison.decode!(resp.resp_body) | |
assert Enum.count(x) == 3 | |
resp = TestClient.get("/users/approvals?#{URI.encode_query(user_id: eliza.user_id, limit: 5, offset: 0)}", eliza) | |
assert resp.status == 200, resp.resp_body | |
assert %{"approvals" => x, "total_rows" => ^approvals_count} = Poison.decode!(resp.resp_body) | |
assert Enum.count(x) == 5 | |
resp = TestClient.get("/users/approvals?#{URI.encode_query(user_id: eliza.user_id, limit: 4, offset: 4)}", eliza) | |
assert resp.status == 200, resp.resp_body | |
assert %{"approvals" => x, "total_rows" => ^approvals_count} = Poison.decode!(resp.resp_body) | |
assert Enum.count(x) == approvals_count - 4 | |
# test request deletion | |
# confirm amount of requests retrieved | |
resp = TestClient.get("/users/orgs/#{entity_id}/requests", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"requests" => r, "total_rows" => ^org_requests_count} = Poison.decode!(resp.resp_body) | |
assert Enum.count(r) == org_requests_count | |
# get info of latest request (deleting org admin) | |
resp = TestClient.get("/users/orgs/#{entity_id}/requests/#{request_id}/responses", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"responses" => r} = Poison.decode!(resp.resp_body) | |
assert Enum.count(r) == 3 | |
# approver should see the request | |
resp = TestClient.get("/users/approvals?#{URI.encode_query(user_id: eliza.user_id)}", eliza) | |
assert resp.status == 200, resp.resp_body | |
assert %{"approvals" => r, "total_rows" => ^approvals_count} = Poison.decode! resp.resp_body | |
assert Enum.count(r) == approvals_count | |
# delete the request | |
resp = TestClient.delete("/users/orgs/#{entity_id}/requests/#{request_id}", alice) | |
assert resp.status == 200, resp.resp_body | |
# amount of requests retrieved should decrease by 1 | |
org_requests_count = org_requests_count - 1 | |
resp = TestClient.get("/users/orgs/#{entity_id}/requests", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"requests" => r, "total_rows" => ^org_requests_count} = Poison.decode!(resp.resp_body) | |
assert Enum.count(r) == org_requests_count | |
resp = TestClient.get("/users/orgs/#{entity_id}/requests/#{request_id}/responses", alice) | |
assert %{"responses" => r} = Poison.decode!(resp.resp_body) | |
assert Enum.count(r) == 0 | |
# approver shouldn't see the request either | |
approvals_count = approvals_count - 1 | |
resp = TestClient.get("/users/approvals?#{URI.encode_query(user_id: eliza.user_id)}", eliza) | |
assert resp.status == 200, resp.resp_body | |
assert %{"approvals" => r, "total_rows" => ^approvals_count} = Poison.decode! resp.resp_body | |
assert Enum.count(r) == approvals_count | |
# filter by status | |
resp = TestClient.get("/users/orgs/#{entity_id}/requests?#{URI.encode_query(status: "pending")}", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"requests" => x, "total_rows" => 0} = Poison.decode!(resp.resp_body) | |
assert Enum.count(x) == 0 | |
resp = TestClient.get("/users/orgs/#{entity_id}/requests?#{URI.encode_query(status: "approved")}", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"requests" => x, "total_rows" => _} = Poison.decode!(resp.resp_body) | |
assert Enum.all?(x, &(&1["status"] == "approved")) | |
resp = TestClient.get("/users/orgs/#{entity_id}/requests?#{URI.encode_query(status: "denied")}", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"requests" => x, "total_rows" => _} = Poison.decode!(resp.resp_body) | |
assert Enum.all?(x, &(&1["status"] == "denied")) | |
# filter by type | |
resp = TestClient.get("/users/orgs/#{entity_id}/requests?#{URI.encode_query(request_type: "change_admin_status")}", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"requests" => x, "total_rows" => _} = Poison.decode!(resp.resp_body) | |
assert Enum.all?(x, &(&1["type"] == "change_admin_status")) | |
resp = TestClient.get("/users/orgs/#{entity_id}/requests?#{URI.encode_query(request_type: "change_org_approval_group_role")}", alice) | |
assert resp.status == 200, resp.resp_body | |
assert %{"requests" => x, "total_rows" => _} = Poison.decode!(resp.resp_body) | |
assert Enum.all?(x, &(&1["type"] == "change_org_approval_group_role")) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment