ubicloud/routes/project/user.rb
Jeremy Evans 81d6d4c58f Show a flash message on successful PAT deletion
Only show the flash message if a token was found.
2024-12-05 09:28:44 -08:00

192 lines
8.1 KiB
Ruby

# frozen_string_literal: true
class Clover
hash_branch(:project_prefix, "user") do |r|
r.on web? do
authorize("Project:user", @project.id)
r.get true do
@user_policies = {}
@project.access_policies_dataset.where(managed: true).each do |policy|
policy.body["acls"].first["subjects"].each do |subject|
# We plan to convert tags to UBIDs in our access policies while
# persisting them. Until this change is implemented, we must locate
# the access tag by its name. I opted for Ruby's 'find' method over
# 'dataset' to repeatedly use cached data.
if (account = Account[@project.access_tags.find { _1.name == subject }&.hyper_tag_id])
@user_policies[account.ubid] = policy.name
end
end
end
@users = Serializers::Account.serialize(@project.accounts_dataset.order_by(:email).all)
@invitations = Serializers::ProjectInvitation.serialize(@project.invitations_dataset.order_by(:email).all)
view "project/user"
end
r.get "token" do
@token_policies = {}
@tokens = current_account.api_keys
@project.access_policies_dataset.where(managed: true).each do |policy|
policy.body["acls"].first["subjects"].each do |subject|
if (token = @tokens.find { _1.hyper_tag_name == subject })
@token_policies[token.ubid] = policy.name
end
end
end
view "project/token"
end
r.get "policy" do
@policy = Serializers::AccessPolicy.serialize(@project.access_policies_dataset.where(managed: false).first) || {body: {acls: []}.to_json}
view "project/policy"
end
r.post true do
email = r.params["email"]
policy = r.params["policy"]
if ProjectInvitation[project_id: @project.id, email: email]
flash["error"] = "'#{email}' already invited to join the project."
r.redirect "#{@project.path}/user"
elsif @project.invitations_dataset.count >= 50
flash["error"] = "You can't have more than 50 pending invitations."
r.redirect "#{@project.path}/user"
end
if (user = Account.exclude(status_id: 3)[email: email])
user.associate_with_project(@project)
if (managed_policy = Authorization::ManagedPolicy.from_name(policy))
managed_policy.apply(@project, [user], append: true)
end
Util.send_email(email, "Invitation to Join '#{@project.name}' Project on Ubicloud",
greeting: "Hello,",
body: ["You're invited by '#{current_account.name}' to join the '#{@project.name}' project on Ubicloud.",
"To join project, click the button below.",
"For any questions or assistance, reach out to our team at support@ubicloud.com."],
button_title: "Join Project",
button_link: "#{Config.base_url}#{@project.path}/dashboard")
else
@project.add_invitation(email: email, policy: policy, inviter_id: current_account_id, expires_at: Time.now + 7 * 24 * 60 * 60)
Util.send_email(email, "Invitation to Join '#{@project.name}' Project on Ubicloud",
greeting: "Hello,",
body: ["You're invited by '#{current_account.name}' to join the '#{@project.name}' project on Ubicloud.",
"To join project, you need to create an account on Ubicloud. Once you create an account, you'll be automatically joined to the project.",
"For any questions or assistance, reach out to our team at support@ubicloud.com."],
button_title: "Create Account",
button_link: "#{Config.base_url}/create-account")
end
flash["notice"] = "Invitation sent successfully to '#{email}'. You need to add some policies to allow new user to operate in the project."
r.redirect "#{@project.path}/user"
end
r.on "token" do
r.post true do
pat = DB.transaction { ApiKey.create_personal_access_token(current_account, project: @project) }
flash["notice"] = "Created personal access token with id #{pat.ubid}"
r.redirect "#{@project.path}/user/token"
end
r.post "update-policies" do
token_policies = r.params["token_policies"] || {}
existing_tokens = current_account.api_keys.map(&:hyper_tag_name)
[Authorization::ManagedPolicy::Admin, Authorization::ManagedPolicy::Member].each do |policy|
tokens = token_policies.select { _2 == policy.name }.keys.map { ApiKey[owner_id: current_account_id, id: UBID.to_uuid(_1)] }
# Do not modify existing user subjects or api_key subjects for other accounts
policy.apply(@project, tokens, remove_subjects: existing_tokens) unless tokens.empty?
end
flash["notice"] = "Personal access token policies updated successfully."
r.redirect "#{@project.path}/user/token"
end
r.delete String do |ubid|
if (token = current_account.api_keys_dataset.with_pk(UBID.to_uuid(ubid)))
token.destroy
flash["notice"] = "Personal access token deleted successfully"
end
204
end
end
r.on "policy" do
r.post "managed" do
user_policies = r.params["user_policies"] || {}
# We iterate over all managed policies, not user_policies, to make sure
# we clear out any policy that no one is using.
[Authorization::ManagedPolicy::Admin, Authorization::ManagedPolicy::Member].each do |policy|
accounts = user_policies.select { _2 == policy.name }.keys.map { Account.from_ubid(_1) }
if policy == Authorization::ManagedPolicy::Admin && accounts.empty?
flash["error"] = "The project must have at least one admin."
redirect_back_with_inputs
end
# Do not modify existing api_token subjects
policy.apply(@project, accounts, remove_subjects: "user/")
end
invitation_policies = r.params["invitation_policies"] || {}
invitation_policies.each do |email, policy|
@project.invitations.find { _1.email == email }&.update(policy: policy)
end
flash["notice"] = "User policies updated successfully."
r.redirect "#{@project.path}/user"
end
r.post "advanced" do
body = r.params["body"]
begin
fail JSON::ParserError unless JSON.parse(body).is_a?(Hash)
rescue JSON::ParserError
flash["error"] = "The policy isn't a valid JSON object."
redirect_back_with_inputs
end
if (policy = @project.access_policies_dataset.where(managed: false).first)
policy.update(body: body)
else
AccessPolicy.create_with_id(project_id: @project.id, name: "advanced", managed: false, body: body)
end
flash["notice"] = "Advanced policy updated for '#{@project.name}'"
r.redirect "#{@project.path}/user/policy"
end
end
r.delete "invitation", String do |email|
@project.invitations_dataset.where(email: email).destroy
# Javascript handles redirect
flash["notice"] = "Invitation for '#{email}' is removed successfully."
204
end
r.delete String do |user_ubid|
next unless (user = Account.from_ubid(user_ubid))
unless @project.accounts.count > 1
response.status = 400
next {error: {message: "You can't remove the last user from '#{@project.name}' project. Delete project instead."}}
end
hyper_tag = user.hyper_tag_name(@project)
@project.access_policies_dataset.where(managed: true).each do |policy|
if policy.body["acls"].first["subjects"].include?(hyper_tag)
policy.body["acls"].first["subjects"].delete(hyper_tag)
policy.modified!(:body)
policy.save_changes
end
end
user.dissociate_with_project(@project)
# Javascript refreshes page
flash["notice"] = "Removed #{user.email} from #{@project.name}"
204
end
end
end
end