Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions shard.lock
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ shards:

office365:
git: https://github.com/placeos/office365.git
version: 1.26.1
version: 1.27.1

openai:
git: https://github.com/spider-gazelle/crystal-openai.git
Expand Down Expand Up @@ -211,19 +211,19 @@ shards:

placeos-frontend-loader:
git: https://github.com/placeos/frontend-loader.git
version: 2.7.1+git.commit.f6a6fb1b2d6f22f5fd312c1247ffce911cf735bb
version: 2.7.1+git.commit.c342bf628278dfd419ff91d6bb033287caa131a4

placeos-log-backend:
git: https://github.com/place-labs/log-backend.git
version: 0.13.0

placeos-models:
git: https://github.com/placeos/models.git
version: 9.87.0
version: 9.88.0

placeos-resource:
git: https://github.com/place-labs/resource.git
version: 3.1.1
version: 3.1.2

pool:
git: https://github.com/ysbaddaden/pool.git
Expand Down Expand Up @@ -267,7 +267,7 @@ shards:

search-ingest:
git: https://github.com/placeos/search-ingest.git
version: 2.11.3+git.commit.bf3c6a409e8e5685adb42fb7d2a3b2ba8b32c24b
version: 2.11.3+git.commit.8d1aa8dfba78cdabd4d8e65181df82d4a1557537

secrets-env: # Overridden
git: https://github.com/spider-gazelle/secrets-env.git
Expand Down
34 changes: 34 additions & 0 deletions spec/controllers/groups/application_doorkeeper_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
require "../../helper"

module PlaceOS::Api
describe Groups::ApplicationDoorkeepers do
base = Groups::ApplicationDoorkeepers.base_route

::Spec.before_each { clear_group_tables }

it "sys_admin can create and destroy a link; non-admin cannot list, create or destroy" do
authority = Model::Authority.find_by_domain("localhost").not_nil!
app = Model::Generator.group_application(authority: authority).save!
doorkeeper = Model::Generator.doorkeeper_application(owner: authority).save!

payload = {
group_application_id: app.id,
doorkeeper_application_id: doorkeeper.id,
}.to_json

_, user_headers = Spec::Authentication.authentication(sys_admin: false, support: false)

client.get(base, headers: user_headers).status_code.should eq 403
client.post(base, body: payload, headers: user_headers).status_code.should eq 403

create = client.post(base, body: payload, headers: Spec::Authentication.headers)
create.status_code.should eq 201

show_path = File.join(base, app.id.to_s, doorkeeper.id.to_s)
client.get(show_path, headers: Spec::Authentication.headers).status_code.should eq 200

delete = client.delete(show_path, headers: Spec::Authentication.headers)
delete.success?.should be_true
end
end
end
34 changes: 34 additions & 0 deletions spec/controllers/groups/application_membership_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
require "../../helper"

module PlaceOS::Api
describe Groups::ApplicationMemberships do
base = Groups::ApplicationMemberships.base_route

::Spec.before_each { clear_group_tables }

it "sys_admin can create and destroy; non-admin cannot" do
authority = Model::Authority.find_by_domain("localhost").not_nil!
app = Model::Generator.group_application(authority: authority).save!
group = Model::Generator.group(authority: authority).save!

payload = {group_id: group.id, application_id: app.id}.to_json

_, user_headers = Spec::Authentication.authentication(sys_admin: false, support: false)
forbidden = client.post(base, body: payload, headers: user_headers)
forbidden.status_code.should eq 403

create = client.post(base, body: payload, headers: Spec::Authentication.headers)
create.status_code.should eq 201

show_path = File.join(base, group.id.to_s, app.id.to_s)
show = client.get(show_path, headers: user_headers)
show.status_code.should eq 200

delete_forbidden = client.delete(show_path, headers: user_headers)
delete_forbidden.status_code.should eq 403

delete = client.delete(show_path, headers: Spec::Authentication.headers)
delete.success?.should be_true
end
end
end
132 changes: 132 additions & 0 deletions spec/controllers/groups/application_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
require "../../helper"

module PlaceOS::Api
describe Groups::Applications do
base = Groups::Applications.base_route

::Spec.before_each { clear_group_tables }

describe "CRUD (sys_admin)" do
it "indexes + shows for any authenticated user" do
authority = Model::Authority.find_by_domain("localhost").not_nil!
app = Model::Generator.group_application(authority: authority).save!

_, user_headers = Spec::Authentication.authentication(sys_admin: false, support: false)
result = client.get(base, headers: user_headers)
result.status_code.should eq 200
Array(Hash(String, JSON::Any)).from_json(result.body).map(&.["id"]).should contain(JSON::Any.new(app.id.to_s))

show = client.get(File.join(base, app.id.to_s), headers: user_headers)
show.status_code.should eq 200
end

it "rejects create for non-sys_admin" do
_, user_headers = Spec::Authentication.authentication(sys_admin: false, support: false)
authority = Model::Authority.find_by_domain("localhost").not_nil!
payload = Model::Generator.group_application(authority: authority).to_json
result = client.post(base, body: payload, headers: user_headers)
result.status_code.should eq 403
end

it "allows create/update/destroy for sys_admin" do
authority = Model::Authority.find_by_domain("localhost").not_nil!
payload = Model::Generator.group_application(authority: authority).to_json
create = client.post(base, body: payload, headers: Spec::Authentication.headers)
create.status_code.should eq 201
created = Model::GroupApplication.from_trusted_json(create.body)

update = client.patch(
File.join(base, created.id.to_s),
body: {name: "renamed"}.to_json,
headers: Spec::Authentication.headers,
)
update.status_code.should eq 200

delete = client.delete(File.join(base, created.id.to_s), headers: Spec::Authentication.headers)
delete.success?.should be_true
Model::GroupApplication.find?(created.id.not_nil!).should be_nil
end
end

it "index supports ?q= substring search on name" do
authority = Model::Authority.find_by_domain("localhost").not_nil!
signage = Model::Generator.group_application(authority: authority)
signage.name = "Signage-#{Random::Secure.hex(3)}"
signage.save!
events = Model::Generator.group_application(authority: authority)
events.name = "Events-#{Random::Secure.hex(3)}"
events.save!

result = client.get("#{base}?q=signage", headers: Spec::Authentication.headers)
result.status_code.should eq 200
ids = Array(Hash(String, JSON::Any)).from_json(result.body).map { |e| e["id"].as_s }
ids.should contain(signage.id.to_s)
ids.should_not contain(events.id.to_s)
end

describe "helper routes" do
it "effective_permissions returns the user's bitmask on a zone" do
authority = Model::Authority.find_by_domain("localhost").not_nil!
app = Model::Generator.group_application(authority: authority).save!
user, headers = Spec::Authentication.authentication(sys_admin: false, support: false)

root_group = Model::Generator.group(authority: authority).save!
Model::Generator.group_application_membership(group: root_group, application: app).save!
Model::Generator.group_user(user: user, group: root_group, permissions: Model::Permissions::Read).save!

zone = Model::Generator.zone.save!
Model::Generator.group_zone(group: root_group, zone: zone, permissions: Model::Permissions::Read).save!

result = client.get(
"#{File.join(base, app.id.to_s)}/effective_permissions?zone_id=#{zone.id}",
headers: headers,
)
result.status_code.should eq 200
body = JSON.parse(result.body)
body["permissions"].as_i.should eq Model::Permissions::Read.to_i
body["zone_ids"].as_a.map(&.as_s).should eq [zone.id]
end

it "effective_permissions supports batch zone_ids" do
authority = Model::Authority.find_by_domain("localhost").not_nil!
app = Model::Generator.group_application(authority: authority).save!
user, headers = Spec::Authentication.authentication(sys_admin: false, support: false)

root_group = Model::Generator.group(authority: authority).save!
Model::Generator.group_application_membership(group: root_group, application: app).save!
Model::Generator.group_user(user: user, group: root_group, permissions: Model::Permissions::All).save!

z1 = Model::Generator.zone.save!
z2 = Model::Generator.zone.save!
Model::Generator.group_zone(group: root_group, zone: z1, permissions: Model::Permissions::Read).save!
Model::Generator.group_zone(group: root_group, zone: z2, permissions: Model::Permissions::Update).save!

result = client.get(
"#{File.join(base, app.id.to_s)}/effective_permissions?zone_ids=#{z1.id},#{z2.id}",
headers: headers,
)
result.status_code.should eq 200
body = JSON.parse(result.body)
body["permissions"].as_i.should eq (Model::Permissions::Read | Model::Permissions::Update).to_i
end

it "accessible_zones lists every reachable zone" do
authority = Model::Authority.find_by_domain("localhost").not_nil!
app = Model::Generator.group_application(authority: authority).save!
user, headers = Spec::Authentication.authentication(sys_admin: false, support: false)

root_group = Model::Generator.group(authority: authority).save!
Model::Generator.group_application_membership(group: root_group, application: app).save!
Model::Generator.group_user(user: user, group: root_group, permissions: Model::Permissions::Read).save!

zone = Model::Generator.zone.save!
Model::Generator.group_zone(group: root_group, zone: zone, permissions: Model::Permissions::Read).save!

result = client.get("#{File.join(base, app.id.to_s)}/accessible_zones", headers: headers)
result.status_code.should eq 200
body = JSON.parse(result.body)
body["zone_ids"].as_a.map(&.as_s).should contain(zone.id.not_nil!)
end
end
end
end
31 changes: 31 additions & 0 deletions spec/controllers/groups/history_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
require "../../helper"

module PlaceOS::Api
describe Groups::History do
base = Groups::History.base_route

::Spec.before_each { clear_group_tables }

it "sys_admin can list all history; non-admin must scope to a managed group" do
authority = Model::Authority.find_by_domain("localhost").not_nil!
admin = Spec::Authentication.user(sys_admin: true)
manager, manager_headers = Spec::Authentication.authentication(sys_admin: false, support: false)

group = Model::Generator.group(authority: authority)
group.acting_user = admin
group.save!

Model::Generator.group_user(user: manager, group: group, permissions: Model::Permissions::Manage).save!

# non-admin with no group_id → 403
client.get(base, headers: manager_headers).status_code.should eq 403

# non-admin with group_id they manage → 200
scoped = client.get("#{base}?group_id=#{group.id}", headers: manager_headers)
scoped.status_code.should eq 200

# sys_admin unfiltered → 200
client.get(base, headers: Spec::Authentication.headers).status_code.should eq 200
end
end
end
65 changes: 65 additions & 0 deletions spec/controllers/groups/invitation_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
require "../../helper"

module PlaceOS::Api
describe Groups::Invitations do
base = Groups::Invitations.base_route

::Spec.before_each { clear_group_tables }

it "manager can create and destroy invitations; response includes plaintext_secret once" do
authority = Model::Authority.find_by_domain("localhost").not_nil!
manager, manager_headers = Spec::Authentication.authentication(sys_admin: false, support: false)

group = Model::Generator.group(authority: authority).save!
Model::Generator.group_user(user: manager, group: group, permissions: Model::Permissions::Manage).save!

payload = {
group_id: group.id,
email: "invited-#{Random::Secure.hex(4)}@example.com",
permissions: Model::Permissions::Read.to_i,
}.to_json
create = client.post(base, body: payload, headers: manager_headers)
create.status_code.should eq 201

body = JSON.parse(create.body)
body["plaintext_secret"].as_s.size.should be > 10
id = body["invitation"]["id"].as_s

delete = client.delete(File.join(base, id), headers: manager_headers)
delete.success?.should be_true
end

it "rejects invitation creation without Manage" do
authority = Model::Authority.find_by_domain("localhost").not_nil!
_, user_headers = Spec::Authentication.authentication(sys_admin: false, support: false)

group = Model::Generator.group(authority: authority).save!
payload = {
group_id: group.id,
email: "invited-#{Random::Secure.hex(4)}@example.com",
permissions: 1,
}.to_json
client.post(base, body: payload, headers: user_headers).status_code.should eq 403
end

it "user can accept an invitation they're eligible for" do
authority = Model::Authority.find_by_domain("localhost").not_nil!
user, headers = Spec::Authentication.authentication(sys_admin: false, support: false)

group = Model::Generator.group(authority: authority).save!
invitation = Model::GroupInvitation.build_with_secret(
group: group,
email: user.email.to_s,
permissions: Model::Permissions::Read,
)
invitation.save!

result = client.post(File.join(base, invitation.id.to_s, "accept"), headers: headers)
result.status_code.should eq 200
gu = Model::GroupUser.from_trusted_json(result.body)
gu.user_id.should eq user.id
gu.group_id.should eq group.id
Model::GroupInvitation.find?(invitation.id.not_nil!).should be_nil
end
end
end
53 changes: 53 additions & 0 deletions spec/controllers/groups/user_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
require "../../helper"

module PlaceOS::Api
describe Groups::Users do
base = Groups::Users.base_route

::Spec.before_each { clear_group_tables }

it "manager can add and remove a user on their group" do
authority = Model::Authority.find_by_domain("localhost").not_nil!
manager, manager_headers = Spec::Authentication.authentication(sys_admin: false, support: false)

group = Model::Generator.group(authority: authority).save!
Model::Generator.group_user(user: manager, group: group, permissions: Model::Permissions::Manage).save!

other = Model::Generator.user(authority: authority).save!
payload = {
user_id: other.id,
group_id: group.id,
permissions: Model::Permissions::Read.to_i,
}.to_json

create = client.post(base, body: payload, headers: manager_headers)
create.status_code.should eq 201

delete_path = File.join(base, other.id.to_s, group.id.to_s)
delete = client.delete(delete_path, headers: manager_headers)
delete.success?.should be_true
end

it "non-manager rejected when managing another user's entry" do
authority = Model::Authority.find_by_domain("localhost").not_nil!
_, user_headers = Spec::Authentication.authentication(sys_admin: false, support: false)

group = Model::Generator.group(authority: authority).save!
other = Model::Generator.user(authority: authority).save!
payload = {user_id: other.id, group_id: group.id, permissions: 1}.to_json
client.post(base, body: payload, headers: user_headers).status_code.should eq 403
end

it "user can see their own entries via user_id=me" do
authority = Model::Authority.find_by_domain("localhost").not_nil!
user, headers = Spec::Authentication.authentication(sys_admin: false, support: false)
group = Model::Generator.group(authority: authority).save!
Model::Generator.group_user(user: user, group: group, permissions: Model::Permissions::Read).save!

result = client.get("#{base}?user_id=me", headers: headers)
result.status_code.should eq 200
rows = Array(Hash(String, JSON::Any)).from_json(result.body)
rows.map { |r| r["user_id"].as_s }.uniq.should eq [user.id.to_s]
end
end
end
Loading
Loading