+ def set_initial_username(requested: false)
+ if !requested.is_a?(String) || requested.empty?
+ email_parts = email.partition("@")
+ local_parts = email_parts.first.partition("+")
+ if email_parts.any?(&:empty?)
+ return
+ elsif not local_parts.first.empty?
+ requested = local_parts.first
+ else
+ requested = email_parts.first
+ end
+ end
+ requested.sub!(/^[^A-Za-z]+/, "")
+ requested.gsub!(/[^A-Za-z0-9]/, "")
+ unless 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)
+ end
+ end
+
+ # Move this user's (i.e., self's) owned items into new_owner_uuid.
+ # Also redirect future uses of this account to
+ # redirect_to_user_uuid, i.e., when a caller authenticates to this
+ # account in the future, the account redirect_to_user_uuid account
+ # will be used instead.
+ #
+ # current_user must have admin privileges, i.e., the caller is
+ # responsible for checking permission to do this.
+ def merge(new_owner_uuid:, redirect_to_user_uuid:)
+ raise PermissionDeniedError if !current_user.andand.is_admin
+ raise "not implemented" if !redirect_to_user_uuid
+ transaction(requires_new: true) do
+ reload
+ raise "cannot merge an already merged user" if self.redirect_to_user_uuid
+
+ new_user = User.where(uuid: redirect_to_user_uuid).first
+ raise "user does not exist" if !new_user
+ raise "cannot merge to an already merged user" if new_user.redirect_to_user_uuid
+
+ # Existing API tokens are updated to authenticate to the new
+ # user.
+ ApiClientAuthorization.
+ where(user_id: id).
+ update_all(user_id: new_user.id)
+
+ # References to the old user UUID in the context of a user ID
+ # (rather than a "home project" in the project hierarchy) are
+ # updated to point to the new user.
+ [
+ [AuthorizedKey, :owner_uuid],
+ [AuthorizedKey, :authorized_user_uuid],
+ [Repository, :owner_uuid],
+ [Link, :owner_uuid],
+ [Link, :tail_uuid],
+ [Link, :head_uuid],
+ ].each do |klass, column|
+ klass.where(column => uuid).update_all(column => new_user.uuid)
+ end
+
+ # References to the merged user's "home project" are updated to
+ # point to new_owner_uuid.
+ ActiveRecord::Base.descendants.reject(&:abstract_class?).each do |klass|
+ next if [ApiClientAuthorization,
+ AuthorizedKey,
+ Link,
+ Log,
+ Repository].include?(klass)
+ next if !klass.columns.collect(&:name).include?('owner_uuid')
+ klass.where(owner_uuid: uuid).update_all(owner_uuid: new_owner_uuid)
+ end
+
+ update_attributes!(redirect_to_user_uuid: new_user.uuid)
+ invalidate_permissions_cache
+ end
+ end
+
+ def redirects_to
+ user = self
+ redirects = 0
+ while (uuid = user.redirect_to_user_uuid)
+ user = User.unscoped.find_by_uuid(uuid)
+ if !user
+ raise Exception.new("user uuid #{user.uuid} redirects to nonexistent uuid #{uuid}")
+ end
+ redirects += 1
+ if redirects > 15
+ raise "Starting from #{self.uuid} redirect_to_user_uuid exceeded maximum number of redirects"
+ end
+ end
+ user
+ end
+
+ def self.register info
+ # login info expected fields, all can be optional but at minimum
+ # must supply either 'identity_url' or 'email'
+ #
+ # email
+ # first_name
+ # last_name
+ # username
+ # alternate_emails
+ # identity_url
+
+ info = info.with_indifferent_access
+
+ primary_user = nil
+
+ # local database
+ identity_url = info['identity_url']
+
+ if identity_url && identity_url.length > 0
+ # Only local users can create sessions, hence uuid_like_pattern
+ # here.
+ user = User.unscoped.where('identity_url = ? and uuid like ?',
+ identity_url,
+ User.uuid_like_pattern).first
+ primary_user = user.redirects_to if user
+ end
+
+ if !primary_user
+ # identity url is unset or didn't find matching record.
+ emails = [info['email']] + (info['alternate_emails'] || [])
+ emails.select! {|em| !em.nil? && !em.empty?}
+
+ User.unscoped.where('email in (?) and uuid like ?',
+ emails,
+ User.uuid_like_pattern).each do |user|
+ if !primary_user
+ primary_user = user.redirects_to
+ elsif primary_user.uuid != user.redirects_to.uuid
+ raise "Ambigious email address, directs to both #{primary_user.uuid} and #{user.redirects_to.uuid}"
+ end
+ end
+ end
+
+ if !primary_user
+ # New user registration
+ primary_user = User.new(:owner_uuid => system_user_uuid,
+ :is_admin => false,
+ :is_active => Rails.configuration.Users.NewUsersAreActive)
+
+ primary_user.set_initial_username(requested: info['username']) if info['username']
+ primary_user.identity_url = info['identity_url'] if identity_url
+ end
+
+ primary_user.email = info['email'] if info['email']
+ primary_user.first_name = info['first_name'] if info['first_name']
+ primary_user.last_name = info['last_name'] if info['last_name']
+
+ if (!primary_user.email or primary_user.email.empty?) and (!primary_user.identity_url or primary_user.identity_url.empty?)
+ raise "Must have supply at least one of 'email' or 'identity_url' to User.register"
+ end
+
+ act_as_system_user do
+ primary_user.save!
+ end
+
+ primary_user
+ end
+