Merge branch '21059-signup-email' refs #21059
[arvados.git] / services / api / app / models / user.rb
index d48dde25a41219304e30d82ea0f8293f27d53edc..4d7b2bbaeb2d7d42a79227cff1f2f09142f19179 100644 (file)
@@ -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