+require 'can_be_an_owner'
+
class User < ArvadosModel
- include AssignUuid
+ include HasUuid
include KindAndEtag
include CommonApiTemplate
+ include CanBeAnOwner
+
serialize :prefs, Hash
has_many :api_client_authorizations
before_update :prevent_privilege_escalation
before_update :prevent_inactive_admin
before_create :check_auto_admin
after_create :add_system_group_permission_link
- after_create AdminNotifier
+ after_create :auto_setup_new_user
+ after_create :send_admin_notifications
+ after_update :send_profile_created_notification
+
has_many :authorized_keys, :foreign_key => :authorized_user_uuid, :primary_key => :uuid
t.add :is_admin
t.add :is_invited
t.add :prefs
+ t.add :writable_by
end
ALL_PERMISSIONS = {read: true, write: true, manage: true}
def full_name
- "#{first_name} #{last_name}"
+ "#{first_name} #{last_name}".strip
end
def is_invited
end
def groups_i_can(verb)
- self.group_permissions.select { |uuid, mask| mask[verb] }.keys
+ my_groups = self.group_permissions.select { |uuid, mask| mask[verb] }.keys
+ if verb == :read
+ my_groups << anonymous_group_uuid
+ end
+ my_groups
end
def can?(actions)
return true if is_admin
actions.each do |action, target|
- target_uuid = target
- if target.respond_to? :uuid
- target_uuid = target.uuid
+ unless target.nil?
+ if target.respond_to? :uuid
+ target_uuid = target.uuid
+ else
+ target_uuid = target
+ target = ArvadosModel.find_by_uuid(target_uuid)
+ end
end
next if target_uuid == self.uuid
next if (group_permissions[target_uuid] and
next if (group_permissions[target.owner_uuid] and
group_permissions[target.owner_uuid][action])
end
+ sufficient_perms = case action
+ when :manage
+ ['can_manage']
+ when :write
+ ['can_manage', 'can_write']
+ when :read
+ ['can_manage', 'can_write', 'can_read']
+ else
+ # (Skip this kind of permission opportunity
+ # if action is an unknown permission type)
+ end
+ if sufficient_perms
+ # Check permission links with head_uuid pointing directly at
+ # the target object. If target is a Group, this is redundant
+ # and will fail except [a] if permission caching is broken or
+ # [b] during a race condition, where a permission link has
+ # *just* been added.
+ if Link.where(link_class: 'permission',
+ name: sufficient_perms,
+ tail_uuid: groups_i_can(action) + [self.uuid],
+ head_uuid: target_uuid).any?
+ next
+ end
+ end
return false
end
true
# Return a hash of {group_uuid: perm_hash} where perm_hash[:read]
# and perm_hash[:write] are true if this user can read and write
# objects owned by group_uuid.
+ #
+ # The permission graph is built by repeatedly enumerating all
+ # permission links reachable from self.uuid, and then calling
+ # search_permissions
def group_permissions
Rails.cache.fetch "groups_for_user_#{self.uuid}" do
permissions_from = {}
todo = {self.uuid => true}
done = {}
+ # Build the equivalence class of permissions starting with
+ # self.uuid. On each iteration of this loop, todo contains
+ # the next set of uuids in the permission equivalence class
+ # to evaluate.
while !todo.empty?
lookup_uuids = todo.keys
lookup_uuids.each do |uuid| done[uuid] = true end
todo = {}
newgroups = []
+ # include all groups owned by the current set of uuids.
Group.where('owner_uuid in (?)', lookup_uuids).each do |group|
newgroups << [group.owner_uuid, group.uuid, 'can_manage']
end
- Link.where('tail_uuid in (?) and link_class = ? and (head_uuid like ? or head_uuid like ?)',
- lookup_uuids,
+ # add any permission links from the current lookup_uuids to a Group.
+ Link.where('link_class = ? and tail_uuid in (?) and ' \
+ '(head_uuid like ? or (name = ? and head_uuid like ?))',
'permission',
+ lookup_uuids,
Group.uuid_like_pattern,
+ 'can_manage',
User.uuid_like_pattern).each do |link|
newgroups << [link.tail_uuid, link.head_uuid, link.name]
end
# delete user signatures, login, repo, and vm perms, and mark as inactive
def unsetup
# delete oid_login_perms for this user
- oid_login_perms = Link.where(tail_uuid: self.email,
- link_class: 'permission',
- name: 'can_login')
- oid_login_perms.each do |perm|
- Link.delete perm
- end
+ Link.destroy_all(tail_uuid: self.email,
+ link_class: 'permission',
+ name: 'can_login')
# delete repo_perms for this user
- repo_perms = Link.where(tail_uuid: self.uuid,
- link_class: 'permission',
- name: 'can_write')
- repo_perms.each do |perm|
- Link.delete perm
- end
+ Link.destroy_all(tail_uuid: self.uuid,
+ link_class: 'permission',
+ name: 'can_manage')
# delete vm_login_perms for this user
- vm_login_perms = Link.where(tail_uuid: self.uuid,
- link_class: 'permission',
- name: 'can_login')
- vm_login_perms.each do |perm|
- Link.delete perm
- end
+ Link.destroy_all(tail_uuid: self.uuid,
+ link_class: 'permission',
+ name: 'can_login')
- # delete "All users' group read permissions for this user
+ # delete "All users" group read permissions for this user
group = Group.where(name: 'All users').select do |g|
g[:uuid].match /-f+$/
end.first
- group_perms = Link.where(tail_uuid: self.uuid,
- head_uuid: group[:uuid],
- link_class: 'permission',
- name: 'can_read')
- group_perms.each do |perm|
- Link.delete perm
- end
+ Link.destroy_all(tail_uuid: self.uuid,
+ head_uuid: group[:uuid],
+ link_class: 'permission',
+ name: 'can_read')
# delete any signatures by this user
- signed_uuids = Link.where(link_class: 'signature',
- tail_uuid: self.uuid)
- signed_uuids.each do |sign|
- Link.delete sign
- end
+ Link.destroy_all(link_class: 'signature',
+ tail_uuid: self.uuid)
+
+ # delete user preferences (including profile)
+ self.prefs = {}
# mark the user as inactive
self.is_active = false
protected
+ def ensure_ownership_path_leads_to_user
+ true
+ end
+
def permission_to_update
# users must be able to update themselves (even if they are
# inactive) in order to create sessions
end
def check_auto_admin
- if User.where("uuid not like '%-000000000000000'").where(:is_admin => true).count == 0 and Rails.configuration.auto_admin_user
- if current_user.email == Rails.configuration.auto_admin_user
+ if not self.uuid.end_with?('anonymouspublic') and
+ User.where("uuid not like '%-000000000000000'").where(:is_admin => true).count == 0
+ if (Rails.configuration.auto_admin_user and self.email == Rails.configuration.auto_admin_user) or
+ Rails.configuration.auto_admin_first_user
self.is_admin = true
self.is_active = true
end
end
def create_oid_login_perm (openid_prefix)
- login_perm_props = {identity_url_prefix: openid_prefix}
+ login_perm_props = { "identity_url_prefix" => openid_prefix}
# Check oid_login_perm
oid_login_perms = Link.where(tail_uuid: self.email,
link_class: 'permission',
- name: 'can_login').where("head_uuid like ?", User.uuid_like_pattern)
+ name: 'can_login').where("head_uuid = ?", self.uuid)
if !oid_login_perms.any?
# create openid login permission
repo_perms = Link.where(tail_uuid: self.uuid,
head_uuid: repo[:uuid],
link_class: 'permission',
- name: 'can_write')
+ name: 'can_manage')
if repo_perms.any?
logger.warn "User already has repository access " +
repo_perms.collect { |p| p[:uuid] }.inspect
repo_perm = Link.create(tail_uuid: self.uuid,
head_uuid: repo[:uuid],
link_class: 'permission',
- name: 'can_write')
+ name: 'can_manage')
logger.info { "repo permission: " + repo_perm[:uuid] }
return repo_perm
end
perm_exists = false
login_perms.each do |perm|
- if perm.properties[:username] == repo_name
- perm_exists = true
+ if perm.properties['username'] == repo_name
+ perm_exists = perm
break
end
end
- if !perm_exists
+ if perm_exists
+ login_perm = perm_exists
+ else
login_perm = Link.create(tail_uuid: self.uuid,
head_uuid: vm[:uuid],
link_class: 'permission',
name: 'can_login',
- properties: {username: repo_name})
+ properties: {'username' => repo_name})
logger.info { "login permission: " + login_perm[:uuid] }
- else
- login_perm = login_perms.first
end
return login_perm
head_uuid: self.uuid)
end
end
+
+ # Send admin notifications
+ def send_admin_notifications
+ AdminNotifier.new_user(self).deliver
+ if not self.is_active then
+ AdminNotifier.new_inactive_user(self).deliver
+ end
+ end
+
+ # Automatically setup new user during creation
+ def auto_setup_new_user
+ return true if !Rails.configuration.auto_setup_new_users
+ return true if !self.email
+ return true if self.uuid == system_user_uuid
+ return true if self.uuid == anonymous_user_uuid
+
+ if Rails.configuration.auto_setup_new_users_with_vm_uuid ||
+ Rails.configuration.auto_setup_new_users_with_repository
+ username = self.email.partition('@')[0] if self.email
+ return true if !username
+
+ blacklisted_usernames = Rails.configuration.auto_setup_name_blacklist
+ if blacklisted_usernames.include?(username)
+ return true
+ elsif !(/^[a-zA-Z][-._a-zA-Z0-9]{0,30}[a-zA-Z0-9]$/.match(username))
+ return true
+ else
+ return true if !(username = derive_unique_username username)
+ end
+ end
+
+ # setup user
+ setup_repo_vm_links(username,
+ Rails.configuration.auto_setup_new_users_with_vm_uuid,
+ Rails.configuration.default_openid_prefix)
+ end
+
+ # Find a username that starts with the given string and does not collide
+ # with any existing repository name or VM login name
+ def derive_unique_username username
+ while true
+ if Repository.where(name: username).empty?
+ login_collisions = Link.where(link_class: 'permission',
+ name: 'can_login').select do |perm|
+ perm.properties['username'] == username
+ end
+ return username if login_collisions.empty?
+ end
+ username = username + SecureRandom.random_number(10).to_s
+ end
+ end
+
+ # Send notification if the user saved profile for the first time
+ def send_profile_created_notification
+ if self.prefs_changed?
+ if self.prefs_was.andand.empty? || !self.prefs_was.andand['profile']
+ profile_notification_address = Rails.configuration.user_profile_notification_address
+ ProfileNotifier.profile_created(self, profile_notification_address).deliver if profile_notification_address
+ end
+ end
+ end
+
end