X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/6ba9fdc006d023fe1a56cd28fb87a54dff5265d8..a6b9fb8166440eef3144150024a875c858db9bb6:/services/api/app/models/user.rb?ds=sidebyside diff --git a/services/api/app/models/user.rb b/services/api/app/models/user.rb index d48dde25a4..4d7b2bbaeb 100644 --- a/services/api/app/models/user.rb +++ b/services/api/app/models/user.rb @@ -21,40 +21,44 @@ class User < ArvadosModel uniqueness: true, allow_nil: true) validate :must_unsetup_to_deactivate + validate :identity_url_nil_if_empty before_update :prevent_privilege_escalation before_update :prevent_inactive_admin - before_update :verify_repositories_empty, :if => Proc.new { |user| - user.username.nil? and user.username_changed? + before_update :prevent_nonadmin_system_root + before_update :verify_repositories_empty, :if => Proc.new { + username.nil? and username_changed? } - before_update :setup_on_activate + after_update :setup_on_activate before_create :check_auto_admin - before_create :set_initial_username, :if => Proc.new { |user| - user.username.nil? and user.email + before_validation :set_initial_username, :if => Proc.new { + new_record? && email } + before_create :active_is_not_nil after_create :after_ownership_change after_create :setup_on_activate after_create :add_system_group_permission_link - after_create :auto_setup_new_user, :if => Proc.new { |user| + after_create :auto_setup_new_user, :if => Proc.new { Rails.configuration.Users.AutoSetupNewUsers and - (user.uuid != system_user_uuid) and - (user.uuid != anonymous_user_uuid) + (uuid != system_user_uuid) and + (uuid != anonymous_user_uuid) and + (uuid[0..4] == Rails.configuration.ClusterID) } after_create :send_admin_notifications before_update :before_ownership_change after_update :after_ownership_change after_update :send_profile_created_notification - after_update :sync_repository_names, :if => Proc.new { |user| - (user.uuid != system_user_uuid) and - user.username_changed? and - (not user.username_was.nil?) + after_update :sync_repository_names, :if => Proc.new { + (uuid != system_user_uuid) and + saved_change_to_username? and + (not username_before_last_save.nil?) } before_destroy :clear_permissions - after_destroy :check_permissions + after_destroy :remove_self_from_permissions - has_many :authorized_keys, :foreign_key => :authorized_user_uuid, :primary_key => :uuid - has_many :repositories, foreign_key: :owner_uuid, primary_key: :uuid + has_many :authorized_keys, foreign_key: 'authorized_user_uuid', primary_key: 'uuid' + has_many :repositories, foreign_key: 'owner_uuid', primary_key: 'uuid' default_scope { where('redirect_to_user_uuid is null') } @@ -70,6 +74,8 @@ class User < ArvadosModel t.add :is_invited t.add :prefs t.add :writable_by + t.add :can_write + t.add :can_manage end ALL_PERMISSIONS = {read: true, write: true, manage: true} @@ -85,6 +91,7 @@ class User < ArvadosModel VAL_FOR_PERM = {:read => 1, :write => 2, + :unfreeze => 3, :manage => 3} @@ -98,6 +105,10 @@ class User < ArvadosModel self.groups_i_can(:read).select { |x| x.match(/-f+$/) }.first) end + def self.ignored_select_attributes + super + ["full_name", "is_invited"] + end + def groups_i_can(verb) my_groups = self.group_permissions(VAL_FOR_PERM[verb]).keys if verb == :read @@ -107,7 +118,6 @@ class User < ArvadosModel end def can?(actions) - return true if is_admin actions.each do |action, target| unless target.nil? if target.respond_to? :uuid @@ -119,24 +129,51 @@ class User < ArvadosModel end next if target_uuid == self.uuid + if action == :write && target && !target.new_record? && + target.respond_to?(:frozen_by_uuid) && + target.frozen_by_uuid_was + # Just an optimization to skip the PERMISSION_VIEW and + # FrozenGroup queries below + return false + end + target_owner_uuid = target.owner_uuid if target.respond_to? :owner_uuid - unless ActiveRecord::Base.connection. + user_uuids_subquery = USER_UUIDS_SUBQUERY_TEMPLATE % {user: "$1", perm_level: "$3"} + + if !is_admin && !ActiveRecord::Base.connection. exec_query(%{ SELECT 1 FROM #{PERMISSION_VIEW} - WHERE user_uuid = $1 and + WHERE user_uuid in (#{user_uuids_subquery}) and ((target_uuid = $2 and perm_level >= $3) or (target_uuid = $4 and perm_level >= $3 and traverse_owned)) }, # "name" arg is a query label that appears in logs: "user_can_query", - [[nil, self.uuid], - [nil, target_uuid], - [nil, VAL_FOR_PERM[action]], - [nil, target_owner_uuid]] + [self.uuid, + target_uuid, + VAL_FOR_PERM[action], + target_owner_uuid] ).any? return false end + + if action == :write + if FrozenGroup.where(uuid: [target_uuid, target_owner_uuid]).any? + # self or parent is frozen + return false + end + elsif action == :unfreeze + # "unfreeze" permission means "can write, but only if + # explicitly un-freezing at the same time" (see + # ArvadosModel#ensure_owner_uuid_is_permitted). If the + # permission query above passed the permission level of + # :unfreeze (which is the same as :manage), and the parent + # isn't also frozen, then un-freeze is allowed. + if FrozenGroup.where(uuid: target_owner_uuid).any? + return false + end + end end true end @@ -144,32 +181,41 @@ SELECT 1 FROM #{PERMISSION_VIEW} def before_ownership_change if owner_uuid_changed? and !self.owner_uuid_was.nil? MaterializedPermission.where(user_uuid: owner_uuid_was, target_uuid: uuid).delete_all - update_permissions self.owner_uuid_was, self.uuid, 0 + update_permissions self.owner_uuid_was, self.uuid, REVOKE_PERM end end def after_ownership_change - if owner_uuid_changed? - update_permissions self.owner_uuid, self.uuid, 3 + if saved_change_to_owner_uuid? + update_permissions self.owner_uuid, self.uuid, CAN_MANAGE_PERM end end def clear_permissions - update_permissions self.owner_uuid, self.uuid, 0 - MaterializedPermission.where("user_uuid = ? or target_uuid = ?", uuid, uuid).delete_all + MaterializedPermission.where("user_uuid = ? and target_uuid != ?", uuid, uuid).delete_all end - def check_permissions + def forget_cached_group_perms + @group_perms = nil + end + + def remove_self_from_permissions + MaterializedPermission.where("target_uuid = ?", uuid).delete_all check_permissions_against_full_refresh end # Return a hash of {user_uuid: group_perms} + # + # note: this does not account for permissions that a user gains by + # having can_manage on another user. def self.all_group_permissions all_perms = {} ActiveRecord::Base.connection. - exec_query("SELECT user_uuid, target_uuid, perm_level + exec_query(%{ +SELECT user_uuid, target_uuid, perm_level FROM #{PERMISSION_VIEW} - WHERE traverse_owned", + WHERE traverse_owned +}, # "name" arg is a query label that appears in logs: "all_group_permissions"). rows.each do |user_uuid, group_uuid, max_p_val| @@ -183,74 +229,130 @@ SELECT 1 FROM #{PERMISSION_VIEW} # and perm_hash[:write] are true if this user can read and write # objects owned by group_uuid. def group_permissions(level=1) - group_perms = {} - ActiveRecord::Base.connection. - exec_query(%{ + @group_perms ||= {} + if @group_perms.empty? + user_uuids_subquery = USER_UUIDS_SUBQUERY_TEMPLATE % {user: "$1", perm_level: 1} + + ActiveRecord::Base.connection. + exec_query(%{ SELECT target_uuid, perm_level FROM #{PERMISSION_VIEW} - WHERE user_uuid = $1 and perm_level >= $2 + WHERE user_uuid in (#{user_uuids_subquery}) and perm_level >= 1 }, - # "name" arg is a query label that appears in logs: - "group_permissions_for_user", - # "binds" arg is an array of [col_id, value] for '$1' vars: - [[nil, uuid], - [nil, level]]). - rows.each do |group_uuid, max_p_val| - group_perms[group_uuid] = PERMS_FOR_VAL[max_p_val.to_i] + # "name" arg is a query label that appears in logs: + "User.group_permissions", + # "binds" arg is an array of [col_id, value] for '$1' vars: + [uuid]). + rows.each do |group_uuid, max_p_val| + @group_perms[group_uuid] = PERMS_FOR_VAL[max_p_val.to_i] + end + end + + case level + when 1 + @group_perms + when 2 + @group_perms.select {|k,v| v[:write] } + when 3 + @group_perms.select {|k,v| v[:manage] } + else + raise "level must be 1, 2 or 3" end - group_perms end # create links - def setup(repo_name: nil, vm_uuid: nil) - repo_perm = create_user_repo_link repo_name - vm_login_perm = create_vm_login_permission_link(vm_uuid, username) if vm_uuid - group_perm = create_user_group_link + def setup(repo_name: nil, vm_uuid: nil, send_notification_email: nil) + newly_invited = Link.where(tail_uuid: self.uuid, + head_uuid: all_users_group_uuid, + link_class: 'permission').empty? - return [repo_perm, vm_login_perm, group_perm, self].compact + # Add can_read link from this user to "all users" which makes this + # user "invited", and (depending on config) a link in the opposite + # direction which makes this user visible to other users. + group_perms = add_to_all_users_group + + # Add git repo + repo_perm = if (!repo_name.nil? || Rails.configuration.Users.AutoSetupNewUsersWithRepository) and !username.nil? + repo_name ||= "#{username}/#{username}" + create_user_repo_link repo_name + end + + # Add virtual machine + if vm_uuid.nil? and !Rails.configuration.Users.AutoSetupNewUsersWithVmUUID.empty? + vm_uuid = Rails.configuration.Users.AutoSetupNewUsersWithVmUUID + end + + vm_login_perm = if vm_uuid && username + create_vm_login_permission_link(vm_uuid, username) + end + + # Send welcome email + if send_notification_email.nil? + send_notification_email = Rails.configuration.Mail.SendUserSetupNotificationEmail + end + + if newly_invited and send_notification_email and !Rails.configuration.Users.UserSetupMailText.empty? + begin + UserNotifier.account_is_setup(self).deliver_now + rescue => e + logger.warn "Failed to send email to #{self.email}: #{e}" + end + end + + forget_cached_group_perms + + return [repo_perm, vm_login_perm, *group_perms, self].compact end # delete user signatures, login, repo, and vm perms, and mark as inactive def unsetup + if self.uuid == system_user_uuid + raise "System root user cannot be deactivated" + end + # delete oid_login_perms for this user # - # note: these permission links are obsolete, they have no effect - # on anything and they are not created for new users. + # note: these permission links are obsolete anyway: they have no + # effect on anything and they are not created for new users. Link.where(tail_uuid: self.email, - link_class: 'permission', - name: 'can_login').destroy_all - - # delete repo_perms for this user - Link.where(tail_uuid: self.uuid, - link_class: 'permission', - name: 'can_manage').destroy_all - - # delete vm_login_perms for this user - Link.where(tail_uuid: self.uuid, - link_class: 'permission', - name: 'can_login').destroy_all + link_class: 'permission', + name: 'can_login').destroy_all - # delete "All users" group read permissions for this user - group = Group.where(name: 'All users').select do |g| - g[:uuid].match(/-f+$/) - end.first + # Delete all sharing permissions so (a) the user doesn't + # automatically regain access to anything if re-setup in future, + # (b) the user doesn't appear in "currently shared with" lists + # shown to other users. + # + # Notably this includes the can_read -> "all users" group + # permission. Link.where(tail_uuid: self.uuid, - head_uuid: group[:uuid], - link_class: 'permission', - name: 'can_read').destroy_all + link_class: 'permission').destroy_all # delete any signatures by this user Link.where(link_class: 'signature', - tail_uuid: self.uuid).destroy_all + tail_uuid: self.uuid).destroy_all + + # delete tokens for this user + ApiClientAuthorization.where(user_id: self.id).destroy_all + # delete ssh keys for this user + AuthorizedKey.where(owner_uuid: self.uuid).destroy_all + AuthorizedKey.where(authorized_user_uuid: self.uuid).destroy_all # delete user preferences (including profile) self.prefs = {} # mark the user as inactive + self.is_admin = false # can't be admin and inactive self.is_active = false + forget_cached_group_perms self.save! end + # Called from ArvadosModel + def set_default_owner + self.owner_uuid = system_user_uuid + end + def must_unsetup_to_deactivate if !self.new_record? && self.uuid[0..4] == Rails.configuration.Login.LoginCluster && @@ -262,10 +364,6 @@ SELECT target_uuid, perm_level self.is_active_was && !self.is_active - group = Group.where(name: 'All users').select do |g| - g[:uuid].match(/-f+$/) - end.first - # When a user is set up, they are added to the "All users" # group. A user that is part of the "All users" group is # allowed to self-activate. @@ -280,16 +378,19 @@ SELECT target_uuid, perm_level # explaining the correct way to deactivate a user. # if Link.where(tail_uuid: self.uuid, - head_uuid: group[:uuid], - link_class: 'permission', - name: 'can_read').any? + head_uuid: all_users_group_uuid, + link_class: 'permission').any? errors.add :is_active, "cannot be set to false directly, use the 'Deactivate' button on Workbench, or the 'unsetup' API call" end end end def set_initial_username(requested: false) - if !requested.is_a?(String) || requested.empty? + if new_record? and requested == false and self.username != nil and self.username != "" + requested = self.username + end + + if (!requested.is_a?(String) || requested.empty?) and email email_parts = email.partition("@") local_parts = email_parts.first.partition("+") if email_parts.any?(&:empty?) @@ -300,42 +401,18 @@ SELECT target_uuid, perm_level requested = email_parts.first end end - requested.sub!(/^[^A-Za-z]+/, "") - requested.gsub!(/[^A-Za-z0-9]/, "") - unless requested.empty? + if requested + requested.sub!(/^[^A-Za-z]+/, "") + requested.gsub!(/[^A-Za-z0-9]/, "") + end + unless !requested || requested.empty? self.username = find_usable_username_from(requested) end end - def update_uuid(new_uuid:) - if !current_user.andand.is_admin - raise PermissionDeniedError - end - if uuid == system_user_uuid || uuid == anonymous_user_uuid - raise "update_uuid cannot update system accounts" - end - if self.class != self.class.resource_class_for_uuid(new_uuid) - raise "invalid new_uuid #{new_uuid.inspect}" - end - transaction(requires_new: true) do - reload - old_uuid = self.uuid - self.uuid = new_uuid - save!(validate: false) - change_all_uuid_refs(old_uuid: old_uuid, new_uuid: new_uuid) - ActiveRecord::Base.connection.exec_update %{ -update #{PERMISSION_VIEW} set user_uuid=$1 where user_uuid = $2 -}, - 'User.update_uuid.update_permissions_user_uuid', - [[nil, new_uuid], - [nil, old_uuid]] - ActiveRecord::Base.connection.exec_update %{ -update #{PERMISSION_VIEW} set target_uuid=$1 where target_uuid = $2 -}, - 'User.update_uuid.update_permissions_target_uuid', - [[nil, new_uuid], - [nil, old_uuid]] - end + def active_is_not_nil + self.is_active = false if self.is_active.nil? + self.is_admin = false if self.is_admin.nil? end # Move this user's (i.e., self's) owned items to new_owner_uuid and @@ -361,6 +438,7 @@ update #{PERMISSION_VIEW} set target_uuid=$1 where target_uuid = $2 raise "cannot merge to an already merged user" if new_user.redirect_to_user_uuid self.clear_permissions + new_user.clear_permissions # If 'self' is a remote user, don't transfer authorizations # (i.e. ability to access the account) to the new user, because @@ -434,10 +512,14 @@ update #{PERMISSION_VIEW} set target_uuid=$1 where target_uuid = $2 end if redirect_to_new_user - update_attributes!(redirect_to_user_uuid: new_user.uuid, username: nil) + update!(redirect_to_user_uuid: new_user.uuid, username: nil) end - update_permissions self.owner_uuid, self.uuid, 3, false - update_permissions new_user.owner_uuid, new_user.uuid, 3 + skip_check_permissions_against_full_refresh do + update_permissions self.uuid, self.uuid, CAN_MANAGE_PERM, nil, true + update_permissions new_user.uuid, new_user.uuid, CAN_MANAGE_PERM, nil, true + update_permissions new_user.owner_uuid, new_user.uuid, CAN_MANAGE_PERM, nil, true + end + update_permissions self.owner_uuid, self.uuid, CAN_MANAGE_PERM, nil, true end end @@ -525,8 +607,148 @@ update #{PERMISSION_VIEW} set target_uuid=$1 where target_uuid = $2 primary_user end + def self.update_remote_user remote_user + remote_user = remote_user.symbolize_keys + remote_user_prefix = remote_user[:uuid][0..4] + + # interaction between is_invited and is_active + # + # either can flag can be nil, true or false + # + # in all cases, we create the user if they don't exist. + # + # invited nil, active nil: don't call setup or unsetup. + # + # invited nil, active false: call unsetup + # + # invited nil, active true: call setup and activate them. + # + # + # invited false, active nil: call unsetup + # + # invited false, active false: call unsetup + # + # invited false, active true: call unsetup + # + # + # invited true, active nil: call setup but don't change is_active + # + # invited true, active false: call setup but don't change is_active + # + # invited true, active true: call setup and activate them. + + should_setup = (remote_user_prefix == Rails.configuration.Login.LoginCluster or + Rails.configuration.Users.AutoSetupNewUsers or + Rails.configuration.Users.NewUsersAreActive or + Rails.configuration.RemoteClusters[remote_user_prefix].andand["ActivateUsers"]) + + should_activate = (remote_user_prefix == Rails.configuration.Login.LoginCluster or + Rails.configuration.Users.NewUsersAreActive or + Rails.configuration.RemoteClusters[remote_user_prefix].andand["ActivateUsers"]) + + remote_should_be_unsetup = (remote_user[:is_invited] == nil && remote_user[:is_active] == false) || + (remote_user[:is_invited] == false) + + remote_should_be_setup = should_setup && ( + (remote_user[:is_invited] == nil && remote_user[:is_active] == true) || + (remote_user[:is_invited] == false && remote_user[:is_active] == true) || + (remote_user[:is_invited] == true)) + + remote_should_be_active = should_activate && remote_user[:is_invited] != false && remote_user[:is_active] == true + + begin + user = User.create_with(email: remote_user[:email], + username: remote_user[:username], + first_name: remote_user[:first_name], + last_name: remote_user[:last_name], + is_active: remote_should_be_active + ).find_or_create_by(uuid: remote_user[:uuid]) + rescue ActiveRecord::RecordNotUnique + retry + end + + user.with_lock do + needupdate = {} + [:email, :username, :first_name, :last_name, :prefs].each do |k| + v = remote_user[k] + if !v.nil? && user.send(k) != v + needupdate[k] = v + end + end + + user.email = needupdate[:email] if needupdate[:email] + + loginCluster = Rails.configuration.Login.LoginCluster + if user.username.nil? || user.username == "" + # Don't have a username yet, set one + needupdate[:username] = user.set_initial_username(requested: remote_user[:username]) + elsif remote_user_prefix != loginCluster + # Upstream is not login cluster, don't try to change the + # username once set. + needupdate.delete :username + end + + if needupdate.length > 0 + begin + user.update!(needupdate) + rescue ActiveRecord::RecordInvalid + if remote_user_prefix == loginCluster && !needupdate[:username].nil? + local_user = User.find_by_username(needupdate[:username]) + # The username of this record conflicts with an existing, + # different user record. This can happen because the + # username changed upstream on the login cluster, or + # because we're federated with another cluster with a user + # by the same username. The login cluster is the source + # of truth, so change the username on the conflicting + # record and retry the update operation. + if local_user.uuid != user.uuid + new_username = "#{needupdate[:username]}#{rand(99999999)}" + Rails.logger.warn("cached username '#{needupdate[:username]}' collision with user '#{local_user.uuid}' - renaming to '#{new_username}' before retrying") + local_user.update!({username: new_username}) + retry + end + end + raise # Not the issue we're handling above + end + end + + if remote_should_be_unsetup + # Remote user is not "invited" or "active" state on their home + # cluster, so they should be unsetup, which also makes them + # inactive. + user.unsetup + else + if !user.is_invited && remote_should_be_setup + user.setup + end + + if !user.is_active && remote_should_be_active + # remote user is active and invited, we need to activate them + user.update!(is_active: true) + end + + if remote_user_prefix == Rails.configuration.Login.LoginCluster and + user.is_active and + !remote_user[:is_admin].nil? and + user.is_admin != remote_user[:is_admin] + # Remote cluster controls our user database, including the + # admin flag. + user.update!(is_admin: remote_user[:is_admin]) + end + end + end + user + end + protected + def self.attributes_required_columns + super.merge( + 'can_write' => ['owner_uuid', 'uuid'], + 'can_manage' => ['owner_uuid', 'uuid'], + ) + end + def change_all_uuid_refs(old_uuid:, new_uuid:) ActiveRecord::Base.descendants.reject(&:abstract_class?).each do |klass| klass.columns.each do |col| @@ -631,6 +853,13 @@ update #{PERMISSION_VIEW} set target_uuid=$1 where target_uuid = $2 true end + def prevent_nonadmin_system_root + if self.uuid == system_user_uuid and self.is_admin_changed? and !self.is_admin + raise "System root user cannot be non-admin" + end + true + end + def search_permissions(start, graph, merged={}, upstream_mask=nil, upstream_path={}) nextpaths = graph[start] return merged if !nextpaths @@ -693,16 +922,26 @@ update #{PERMISSION_VIEW} set target_uuid=$1 where target_uuid = $2 login_perm end - # add the user to the 'All users' group - def create_user_group_link - return (Link.where(tail_uuid: self.uuid, - head_uuid: all_users_group[:uuid], + def add_to_all_users_group + resp = [Link.where(tail_uuid: self.uuid, + head_uuid: all_users_group_uuid, link_class: 'permission', - name: 'can_read').first or + name: 'can_write').first || Link.create(tail_uuid: self.uuid, - head_uuid: all_users_group[:uuid], + head_uuid: all_users_group_uuid, link_class: 'permission', - name: 'can_read')) + name: 'can_write')] + if Rails.configuration.Users.ActivatedUsersAreVisibleToOthers + resp += [Link.where(tail_uuid: all_users_group_uuid, + head_uuid: self.uuid, + link_class: 'permission', + name: 'can_read').first || + Link.create(tail_uuid: all_users_group_uuid, + head_uuid: self.uuid, + link_class: 'permission', + name: 'can_read')] + end + return resp end # Give the special "System group" permission to manage this user and @@ -719,8 +958,9 @@ update #{PERMISSION_VIEW} set target_uuid=$1 where target_uuid = $2 # Send admin notifications def send_admin_notifications - AdminNotifier.new_user(self).deliver_now - if not self.is_active then + if self.is_invited then + AdminNotifier.new_user(self).deliver_now + else AdminNotifier.new_inactive_user(self).deliver_now end end @@ -728,7 +968,8 @@ update #{PERMISSION_VIEW} set target_uuid=$1 where target_uuid = $2 # Automatically setup if is_active flag turns on def setup_on_activate return if [system_user_uuid, anonymous_user_uuid].include?(self.uuid) - if is_active && (new_record? || is_active_changed?) + if is_active && + (new_record? || saved_change_to_is_active? || will_save_change_to_is_active?) setup end end @@ -736,23 +977,12 @@ update #{PERMISSION_VIEW} set target_uuid=$1 where target_uuid = $2 # Automatically setup new user during creation def auto_setup_new_user setup - if username - create_vm_login_permission_link(Rails.configuration.Users.AutoSetupNewUsersWithVmUUID, - username) - repo_name = "#{username}/#{username}" - if Rails.configuration.Users.AutoSetupNewUsersWithRepository and - Repository.where(name: repo_name).first.nil? - repo = Repository.create!(name: repo_name, owner_uuid: uuid) - Link.create!(tail_uuid: uuid, head_uuid: repo.uuid, - link_class: "permission", name: "can_manage") - end - 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'] + if saved_change_to_prefs? + if prefs_before_last_save.andand.empty? || !prefs_before_last_save.andand['profile'] profile_notification_address = Rails.configuration.Users.UserProfileNotificationAddress ProfileNotifier.profile_created(self, profile_notification_address).deliver_now if profile_notification_address and !profile_notification_address.empty? end @@ -767,11 +997,17 @@ update #{PERMISSION_VIEW} set target_uuid=$1 where target_uuid = $2 end def sync_repository_names - old_name_re = /^#{Regexp.escape(username_was)}\// + old_name_re = /^#{Regexp.escape(username_before_last_save)}\// name_sub = "#{username}/" repositories.find_each do |repo| repo.name = repo.name.sub(old_name_re, name_sub) repo.save! end end + + def identity_url_nil_if_empty + if identity_url == "" + self.identity_url = nil + end + end end