Skip to content
Merged
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
75 changes: 75 additions & 0 deletions spec/user_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,81 @@ module PlaceOS::Model
end
end

describe "sensitive field serialisation" do
# Secrets that must never leave the backend via any serialisation view
secret_fields = %w(password password_digest access_token refresh_token expires_at expires)
# Fields that only admin / support users may see
admin_only_fields = %w(sys_admin support misc login_name card_number ui_theme)

secret_user = -> do
user = Generator.user
user.password = "s3cr3t-p@ssw0rd"
user.access_token = "ms-access-token-leak"
user.refresh_token = "ms-refresh-token-leak"
user.expires_at = 1.hour.from_now.to_unix
user.expires = true
user.misc = "admin notes"
user.login_name = "login-#{Random.rand(99999)}"
user.card_number = "card-#{Random.rand(99999)}"
user.save!
user.password_digest.should_not be_nil
user
end

views = {
"public" => ->(u : User) { u.to_public_json },
"public_struct" => ->(u : User) { u.to_public_struct.to_json },
"public_meta" => ->(u : User) { u.to_public_metadata_json },
"public_meta_st" => ->(u : User) { u.to_public_metadata_struct.to_json },
"admin" => ->(u : User) { u.to_admin_json },
"admin_struct" => ->(u : User) { u.to_admin_struct.to_json },
"admin_meta" => ->(u : User) { u.to_admin_metadata_json },
"admin_meta_st" => ->(u : User) { u.to_admin_metadata_struct.to_json },
"group" => ->(u : User) { u.to_group_json },
"group_struct" => ->(u : User) { u.to_group_struct.to_json },
}

it "never lists a secret in PUBLIC_DATA or ADMIN_DATA" do
exposed = (User::PUBLIC_DATA + User::ADMIN_DATA).map(&.to_s)
(exposed & secret_fields).should be_empty
end

it "keeps admin only fields out of PUBLIC_DATA" do
(User::PUBLIC_DATA.map(&.to_s) & admin_only_fields).should be_empty
end

views.each do |view, render|
it "never exposes password digests or SSO tokens via the #{view} view" do
user = secret_user.call
json = render.call(user)
keys = JSON.parse(json).as_h.keys

(keys & secret_fields).should be_empty
json.should_not contain(user.password_digest.as(String))
json.should_not contain("ms-access-token-leak")
json.should_not contain("ms-refresh-token-leak")
json.should_not contain("s3cr3t-p@ssw0rd")
end
end

{"public", "public_struct", "public_meta", "public_meta_st"}.each do |view|
it "hides admin only fields from the #{view} view" do
user = secret_user.call
keys = JSON.parse(views[view].call(user)).as_h.keys
(keys & admin_only_fields).should be_empty
end
end

{"admin", "admin_struct", "admin_meta", "admin_meta_st"}.each do |view|
it "includes admin only fields in the #{view} view" do
user = secret_user.call
parsed = JSON.parse(views[view].call(user)).as_h
admin_only_fields.each { |field| parsed.has_key?(field).should be_true }
parsed["misc"].should eq "admin notes"
end
end
end

it "should create a new user with a password" do
existing = Authority.find_by_domain("localhost")
authority = existing || Generator.authority.save!
Expand Down
Loading