1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: AGPL-3.0
6 require 'kind_and_etag'
8 class AddUsernameToUsers < ActiveRecord::Migration[4.2]
9 include CurrentApiClient
11 SEARCH_INDEX_COLUMNS =
12 ["uuid", "owner_uuid", "modified_by_client_uuid",
13 "modified_by_user_uuid", "email", "first_name", "last_name",
14 "identity_url", "default_owner_uuid"]
16 class ArvadosModel < ActiveRecord::Base
17 self.abstract_class = true
18 extend HasUuid::ClassMethods
19 include CurrentApiClient
21 before_create do |record|
22 record.uuid ||= record.class.generate_uuid
23 record.owner_uuid ||= system_user_uuid
25 serialize :properties, Hash
28 # Clean up the name of the stub model class so we generate correct UUIDs.
29 super.rpartition("::").last
33 class Log < ArvadosModel
34 def self.log_for(thing, age="old")
35 { "#{age}_etag" => thing.etag,
36 "#{age}_attributes" => thing.attributes,
40 def self.log_create(thing)
41 new_log("create", thing, log_for(thing, "new"))
44 def self.log_update(thing, start_state)
45 new_log("update", thing, start_state.merge(log_for(thing, "new")))
48 def self.log_destroy(thing)
49 new_log("destroy", thing, log_for(thing, "old"))
54 def self.new_log(event_type, thing, properties)
55 create!(event_type: event_type,
57 object_uuid: thing.uuid,
58 object_owner_uuid: thing.owner_uuid,
59 properties: properties)
63 class Link < ArvadosModel
66 class User < ArvadosModel
69 def sanitize_username(username)
71 sub(/^[^A-Za-z]+/, "").
72 gsub(/[^A-Za-z0-9]/, "")
75 def usernames_wishlist(user)
76 usernames = Hash.new(0)
77 usernames[user.email.split("@", 2).first] += 1
79 where(tail_uuid: user.uuid, link_class: "permission", name: "can_login").
80 find_each do |login_perm|
81 username = login_perm.properties["username"]
82 usernames[username] += 2 if (username and not username.empty?)
85 sort_by { |n| -usernames[n] }.
86 map { |n| sanitize_username(n) }.
90 def increment_username(username)
91 @username_suffixes[username] += 1
92 "%s%i" % [username, @username_suffixes[username]]
95 def each_wanted_username(user)
96 usernames = usernames_wishlist(user)
97 usernames.each { |n| yield n }
98 base_username = usernames.first || "arvadosuser"
99 loop { yield increment_username(base_username) }
102 def recreate_search_index(columns)
103 remove_index :users, name: "users_search_index"
104 add_index :users, columns, name: "users_search_index"
108 @username_suffixes = Hash.new(1)
109 add_column :users, :username, :string, null: true
110 add_index :users, :username, unique: true
111 recreate_search_index(SEARCH_INDEX_COLUMNS + ["username"])
113 [Link, Log, User].each { |m| m.reset_column_information }
114 User.validates(:username, uniqueness: true, allow_nil: true)
115 User.where(is_active: true).order(created_at: :asc).find_each do |user|
116 start_log = Log.log_for(user)
117 each_wanted_username(user) do |username|
118 user.username = username
122 Log.log_update(user, start_log)
127 remove_index :users, :username
128 recreate_search_index(SEARCH_INDEX_COLUMNS)
129 remove_column :users, :username