Select Git revision
user.rb 34.29 KiB
require 'kconv'
require_dependency 'api_exception'
class UserBasicStrategy
def is_in_group?(user, group)
user.groups_users.where(group_id: group.id).exists?
end
def local_role_check(_role, _object)
false # all is checked, nothing remote
end
def local_permission_check(_roles, _object)
false # all is checked, nothing remote
end
def groups(user)
user.groups
end
end
class User < ActiveRecord::Base
include ActiveModel::Dirty
include CanRenderModel
PASSWORD_HASH_TYPES = ['md5', 'md5crypt', 'sha256crypt', 'invalid']
STATES = {
'unconfirmed' => 1,
'confirmed' => 2,
'locked' => 3,
'deleted' => 4,
'ichainrequest' => 5,
'retrieved_password' => 6
}
has_many :taggings, :dependent => :destroy
has_many :tags, :through => :taggings
has_many :watched_projects, dependent: :destroy, inverse_of: :user
has_many :groups_users, inverse_of: :user
has_many :roles_users, inverse_of: :user
has_many :relationships, inverse_of: :user, dependent: :destroy
has_many :comments, dependent: :destroy, inverse_of: :user
has_many :status_messages
has_many :messages
has_many :tokens, :dependent => :destroy, inverse_of: :user
has_many :event_subscriptions, inverse_of: :user
# users have a n:m relation to group
has_and_belongs_to_many :groups, -> { uniq }
# users have a n:m relation to roles
has_and_belongs_to_many :roles, -> { uniq }
# users have 0..1 user_registration records assigned to them
has_one :user_registration
after_create :create_home_project
def create_home_project
# avoid errors during seeding
return if [ "_nobody_", "Admin" ].include? self.login
# may be disabled via Configuration setting
return unless can_create_project?(self.home_project_name)
# find or create the project
project = Project.find_by(name: self.home_project_name)
unless project
project = Project.create(name: self.home_project_name)
# make the user maintainer
project.relationships.create(user: self,
role: Role.find_by_title('maintainer'))
project.store({login: self.login})
end
true
end
# the default state of a user based on the api configuration
def self.default_user_state
return STATES['unconfirmed'] if ::Configuration.registration == "confirmation"
STATES['confirmed']
end
# When a record object is initialized, we set the state and the login failure
# count to unconfirmed/0 when it has not been set yet.
before_validation(:on => :create) do
self.state = STATES['unconfirmed'] if self.state.nil?
self.password_hash_type = 'md5' if self.password_hash_type.to_s == ''
self.login_failure_count = 0 if self.login_failure_count.nil?
end
# Set the last login time etc. when the record is created at first.
def before_create
self.last_logged_in_at = Time.now
end
# Overriding the default accessor to ensure ActiveModel::Dirty get's notified
# when password_hash_type changes. Changing the password hash type is only
# possible if a new password has been provided.
def password_hash_type=(value)
password_hash_type_will_change!
write_attribute(:password_hash_type, value)
end
# Inform ActiveModel::Dirty that changes are persistent now
after_save :changes_applied
# Generate accessors for the password confirmation property.
attr_accessor :password_confirmation
scope :all_without_nobody, -> { where("login != ?", nobody_login) }
# Overriding the default accessor to ensure ActiveModel::Dirty get's notified
# about changes of password attribute
def password=(value)
password_will_change!
write_attribute(:password, value)
end
# Method to update the password and confirmation at the same time. Call
# this method when you update the password from code and don't need
# password_confirmation - which should really only be used when data
# comes from forms.
#
# A ussage example:
#
# user = User.find(1)
# user.update_password "n1C3s3cUreP4sSw0rD"
# user.save
#
def update_password(pass)
password_will_change!
if password_invalid?
self.password_hash_type = 'sha256crypt'
end
self.password_crypted = hash_string(pass).crypt('os')
self.password_confirmation = hash_string(pass)
self.password = hash_string(pass)
end
# This method returns true if the user is assigned the role with one of the
# role titles given as parameters. False otherwise.
def has_role?(*role_titles)
obj = all_roles.detect do |role|
role_titles.include?(role.title)
end
return !obj.nil?
end
# This method creates a new registration token for the current user. Raises
# a MultipleRegistrationTokens Exception if the user already has a
# registration token assigned to him.
#
# Use this method instead of creating user_registration objects directly!
def create_user_registration
raise unless user_registration.nil?
token = UserRegistration.new
self.user_registration = token
end
# This method expects the token for the current user. If the token is
# correct, the user's state will be set to "confirmed" and the associated
# "user_registration" record will be removed.
# Returns "true" on success and "false" on failure/or the user is already
# confirmed and/or has no "user_registration" record.
def confirm_registration(token)
return false if self.user_registration.nil?
return false if user_registration.token != token
return false unless state_transition_allowed?(state, STATES['confirmed'])
self.state = STATES['confirmed']
self.save!
user_registration.destroy
return true
end
# Returns the default state of new User objects.
def self.default_state
STATES['unconfirmed']
end
# Returns true when users with the given state may log in. False otherwise.
# The given parameter must be an integer.
def self.state_allows_login?(state)
[ STATES['confirmed'], STATES['retrieved_password'] ].include?(state)
end
# Overwrite the state setting so it backs up the initial state from
# the database.
def state=(value)
@old_state = state if @old_state.nil?
write_attribute(:state, value)
end
def self.create_external_user(attributes = {})
user = create_user_with_fake_pw!(attributes.merge(state: default_user_state))
if user.errors.empty?
logger.debug("Created new user...")
return user
else
logger.debug("Creating User failed with: ")
all_errors = user.errors.full_messages.map do |msg|
logger.debug(msg)
msg
end
logger.info("Cannot create external userid: '#{login}' on OBS<br>#{all_errors.join(', ')}")
return
end
end
def self.create_user_with_fake_pw!(attributes = {})
# Generate and store a 24 char fake pw in the OBS DB that no-one knows
password = SecureRandom.base64
create!(attributes.merge(password: password, password_confirmation: password))
end
# This static method tries to find a user with the given login and password
# in the database. Returns the user or nil if he could not be found
def self.find_with_credentials(login, password)
# Find user
user = if CONFIG['ldap_mode'] == :on
find_with_credentials_via_ldap(login, password)
else
find_by_login(login)
end
# If the user could be found and the passwords equal then return the user
if user && user.password_equals?(password)
user.mark_login!
return user
end
# Otherwise increase the login count - if the user could be found - and return nil
if user
user.login_failure_count = user.login_failure_count + 1
user.save!
end
return
end
def self.find_with_credentials_via_ldap(login, password)
user = find_by_login(login)
ldap_info = nil
if CONFIG['ldap_mode'] == :on
begin
require 'ldap'
logger.debug( "Using LDAP to find #{login}" )
ldap_info = UserLdapStrategy.find_with_ldap(login, password)
rescue LoadError
logger.warn "ldap_mode selected but 'ruby-ldap' module not installed."
rescue
logger.debug "#{login} not found in LDAP."
end
end
if ldap_info
# We've found an ldap authenticated user - find or create an OBS userDB entry.
if user
user.mark_login!
# Check for ldap updates
user.assign_attributes(email: ldap_info[0], realname: ldap_info[1])
user.save if user.changed?
return user
elsif ::Configuration.registration == "deny"
logger.debug("No user found in database, creation disabled")
return
else
logger.debug("No user found in database, creating")
logger.debug("Email: #{ldap_info[0]}")
logger.debug("Name : #{ldap_info[1]}")
return create_external_user(login: login,
email: ldap_info[0],
realname: ldap_info[1],
adminnote: "User created via LDAP")
end
end
end
def self.find_with_omniauth(auth)
if auth
email = auth['info']['email']
user = find_by_email(email)
if user
user.mark_login!
return user
end
end
end
def self.create_with_omniauth(auth, login)
provider = CONFIG['sso_auth'][auth['provider']]['description'] || OmniAuth::Utils.camelize(auth['provider'])
email = auth['info']['email']
logger.debug("Creating OmniAuth user for #{provider}")
logger.debug("Email: #{email}")
logger.debug("Name : #{auth['info']['name']}")
return create_external_user(login: login,
email: email,
realname: auth['info']['name'],
password_hash_type: 'invalid',
adminnote: "User created via #{provider}")
end
# This method checks whether the given value equals the password when
# hashed with this user's password hash type. Returns a boolean.
def password_equals?(value)
hash_string(value) == self.password && !password_invalid?
end
def password_invalid?
self.password_hash_type == 'invalid'
end
# Sets the last login time and saves the object. Note: Must currently be
# called explicitely!
def did_log_in
self.last_logged_in_at = DateTime.now
self.class.execute_without_timestamps { save }
end
# Returns true if the the state transition from "from" state to "to" state
# is valid. Returns false otherwise. +new_state+ must be the integer value
# of the state as returned by +User::STATES['state_name']+.
#
# Note that currently no permission checking is included here; It does not
# matter what permissions the currently logged in user has, only that the
# state transition is legal in principle.
def state_transition_allowed?(from, to)
from = from.to_i
to = to.to_i
desired_state = STATES.key(to)
return true if from == to # allow keeping state
case from
when STATES['unconfirmed']
true
when STATES['confirmed']
["retrieved_password", "locked", "deleted", "ichainrequest"].include?(desired_state)
when STATES['locked']
["confirmed", "deleted"].include?(desired_state)
when STATES['deleted']
desired_state == "confirmed"
when STATES['ichainrequest']
["locked", "confirmed", "deleted"].include?(desired_state)
when 0
desired_state.present?
else
false
end
end
# Model Validation
# Overriding this method to do some more validation: Password equals
# password_confirmation, state an password hash type being in the range
# of allowed values.
validate do
# validate state and password has type to be in the valid range of values
errors.add(:password_hash_type, 'must be in the list of hash types.') unless password_hash_type.in?(PASSWORD_HASH_TYPES)
# check that the state transition is valid
errors.add(:state, 'must be a valid new state from the current state.') unless state_transition_allowed?(@old_state, state)
# validate the password
if password_changed? and not password.nil?
errors.add(:password, 'must match the confirmation.') unless password_confirmation == password
end
# check that the password hash type has not been set if no new password
# has been provided
if password_hash_type_changed? && (!password_changed? || password_was.nil?)
errors.add(:password_hash_type, 'cannot be changed unless a new password has been provided.')
end
end
validates :login, :password, :password_hash_type, :state,
presence: { message: 'must be given' }
validates :login,
uniqueness: { message: 'is the name of an already existing user.' }
validates :login,
format: { with: %r{\A[\w \$\^\-\.#\*\+&'"]*\z},
message: 'must not contain invalid characters.' }
validates :login,
length: { in: 2..100, allow_nil: true,
too_long: 'must have less than 100 characters.',
too_short: 'must have more than two characters.' }
# We want a valid email address. Note that the checking done here is very
# rough. Email adresses are hard to validate now domain names may include
# language specific characters and user names can be about anything anyway.
# However, this is not *so* bad since users have to answer on their email
# to confirm their registration.
validates :email,
format: { with: %r{\A([\w\-\.\#\$%&!?*\'\+=(){}|~]+)@([0-9a-zA-Z\-\.\#\$%&!?*\'=(){}|~]+)+\z},
message: 'must be a valid email address.',
allow_blank: true }
# We want to validate the format of the password and only allow alphanumeric
# and some punctiation characters.
# The format must only be checked if the password has been set and the record
# has not been stored yet.
validates :password,
format: { with: %r{\A[\w\.\- /+=!?(){}|~*]+\z},
message: 'must not contain invalid characters.',
if: Proc.new { |user| user.password_changed? && !user.password_was.nil? } }
# We want the password to have between 6 and 64 characters.
# The length must only be checked if the password has been set and the record
# has not been stored yet.
validates :password,
length: { within: 6..64,
too_long: 'must have between 6 and 64 characters.',
too_short: 'must have between 6 and 64 characters.',
if: Proc.new { |user| user.password_changed? && !user.password_was.nil? } }
class << self
def current
Thread.current[:user]
end
def current=(user)
Thread.current[:user] = user
end
def nobody_login
'_nobody_'
end
def authenticate(user_login, password)
user = User.find_with_credentials(user_login, password)
# User account is not confirmed yet
return if user.try(:state) == STATES['unconfirmed']
Rails.logger.debug "Authentificated user '#{user.try(:login)}'"
User.current = user
end
def get_default_admin
admin = CONFIG['default_admin'] || 'Admin'
user = find_by_login(admin)
raise NotFoundError.new("Admin not found, user #{admin} has not admin permissions") unless user.is_admin?
return user
end
def find_nobody!
Thread.current[:nobody_user] ||= User.create_with(email: "nobody@localhost",
realname: "Anonymous User",
state: "3",
password: "123456",
password_confirmation: "123456").find_or_create_by(login: nobody_login)
Thread.current[:nobody_user]
end
def find_by_login!(login)
user = find_by_login(login)
if user.nil? or user.state == STATES['deleted']
raise NotFoundError.new("Couldn't find User with login = #{login}")
end
return user
end
def get_by_login(login)
user = find_by_login!(login)
# FIXME: Move permission checks to controller level
unless User.current.is_admin? or user == User.current
raise NoPermission.new "User #{login} can not be accessed by #{User.current.login}"
end
return user
end
def find_by_email(email)
return where(:email => email).first
end
def realname_for_login(login)
User.find_by_login(login).realname
end
end
# After validation, the password should be encrypted
after_validation(:on => :create) do
if errors.empty? and password_changed? && !password_was.nil?
# generate a new 10-char long hash only Base64 encoded so things are compatible
self.password_salt = [Array.new(10){rand(256).chr}.join].pack('m')[0..9]
# vvvvvv added this to maintain the password list for lighttpd
write_attribute(:password_crypted, password.crypt('os'))
# ^^^^^^
# write encrypted password to object property
write_attribute(:password, hash_string(password))
self.password_confirmation = nil
else
logger.debug "Error - skipping to create user #{errors.inspect} #{password} #{password_was}"
end
end
def to_axml(_opts = {})
render_axml
end
def render_axml( watchlist = false )
# CanRenderModel
render_xml(watchlist: watchlist)
end
def state_name
STATES.invert[state]
end
def home_project_name
"home:#{self.login}"
end
def branch_project_name(branch)
home_project_name + ":branches:" + branch
end
# updates users email address and real name using data transmitted by authentification proxy
def update_user_info_from_proxy_env(env)
proxy_email = env['HTTP_X_EMAIL']
if not proxy_email.blank? and self.email != proxy_email
logger.info "updating email for user #{self.login} from proxy header: old:#{self.email}|new:#{proxy_email}"
self.email = proxy_email
self.save
end
if not env['HTTP_X_FIRSTNAME'].blank? and not env['HTTP_X_LASTNAME'].blank?
realname = env['HTTP_X_FIRSTNAME'] + ' ' + env['HTTP_X_LASTNAME']
if self.realname != realname
self.realname = realname
self.save
end
end
end
#####################
# permission checks #
#####################
def is_admin?
if @is_admin.nil? # false is fine
@is_admin = roles.where(title: 'Admin').exists?
end
@is_admin
end
def is_nobody?
self.login == '_nobody_'
end
def is_active?
self.state == STATES['confirmed']
end
# used to avoid
def is_admin=(is_she)
@is_admin = is_she
end
def is_in_group?(group)
if group.nil?
return false
end
if group.kind_of? String
group = Group.find_by_title(group)
return false unless group
end
if group.kind_of? Fixnum
group = Group.find(group)
end
unless group.kind_of? Group
raise ArgumentError, "illegal parameter type to User#is_in_group?: #{group.class}"
end
lookup_strategy.is_in_group?(self, group)
end
# This method returns true if the user is granted the permission with one
# of the given permission titles.
def has_global_permission?(perm_string)
logger.debug "has_global_permission? #{perm_string}"
self.roles.detect do |role|
return true if role.static_permissions.where('static_permissions.title = ?', perm_string).first
end
end
# project is instance of Project
def can_modify_project?(project, ignoreLock = nil)
unless project.kind_of? Project
raise ArgumentError, "illegal parameter type to User#can_modify_project?: #{project.class.name}"
end
if project.new_record?
# Project.check_write_access(!) should have been used?
raise NotFoundError, "Project is not stored yet"
end
can_modify_project_internal(project, ignoreLock)
end
# package is instance of Package
def can_modify_package?(package, ignoreLock = nil)
return false if package.nil? # happens with remote packages easily
unless package.kind_of? Package
raise ArgumentError, "illegal parameter type to User#can_modify_package?: #{package.class.name}"
end
return false if not ignoreLock and package.is_locked?
return true if is_admin?
return true if has_global_permission? 'change_package'
return true if has_local_permission? 'change_package', package
return false
end
# project is instance of Project
def can_create_package_in?(project, ignoreLock = nil)
unless project.kind_of? Project
raise ArgumentError, "illegal parameter type to User#can_change?: #{project.class.name}"
end
return false if not ignoreLock and project.is_locked?
return true if is_admin?
return true if has_global_permission? 'create_package'
return true if has_local_permission? 'create_package', project
return false
end
# project_name is name of the project
def can_create_project?(project_name)
## special handling for home projects
return true if project_name == self.home_project_name && Configuration.allow_user_to_create_home_project
return true if /^#{self.home_project_name}:/.match(project_name) && Configuration.allow_user_to_create_home_project
return true if has_global_permission?('create_project')
parent_project = Project.new(name: project_name).parent
return false if parent_project.nil?
return true if is_admin?
return has_local_permission?('create_project', parent_project)
end
def can_modify_attribute_definition?(object)
return can_create_attribute_definition?(object)
end
def can_create_attribute_definition?(object)
if object.kind_of? AttribType
object = object.attrib_namespace
end
if not object.kind_of? AttribNamespace
raise ArgumentError, "illegal parameter type to User#can_change?: #{object.class.name}"
end
return true if is_admin?
abies = object.attrib_namespace_modifiable_bies.includes([:user, :group])
abies.each do |mod_rule|
next if mod_rule.user and mod_rule.user != self
next if mod_rule.group and not is_in_group? mod_rule.group
return true
end
return false
end
def can_create_attribute_in?(object, opts)
if not object.kind_of? Project and not object.kind_of? Package
raise ArgumentError, "illegal parameter type to User#can_change?: #{object.class.name}"
end
unless opts[:namespace]
raise ArgumentError, 'no namespace given'
end
unless opts[:name]
raise ArgumentError, 'no name given'
end
# find attribute type definition
atype = AttribType.find_by_namespace_and_name!(opts[:namespace], opts[:name])
return true if is_admin?
# check modifiable_by rules
abies = atype.attrib_type_modifiable_bies.includes([:user, :group, :role])
if abies.empty?
# no rules set for attribute, just check package maintainer rules
if object.kind_of? Project
return can_modify_project?(object)
else
return can_modify_package?(object)
end
else
abies.each do |mod_rule|
next if mod_rule.user and mod_rule.user != self
next if mod_rule.group and not is_in_group? mod_rule.group
next if mod_rule.role and not has_local_role?(mod_rule.role, object)
return true
end
end
# never reached
return false
end
def can_download_binaries?(package)
return true if is_admin?
return true if has_global_permission? 'download_binaries'
return true if has_local_permission?('download_binaries', package)
return false
end
def can_source_access?(package)
return true if is_admin?
return true if has_global_permission? 'source_access'
return true if has_local_permission?('source_access', package)
return false
end
def can_access?(parm)
return true if is_admin?
return true if has_global_permission? 'access'
return true if has_local_permission?('access', parm)
return false
end
def can_access_downloadbinany?(parm)
return true if is_admin?
if parm.kind_of? Package
return true if can_download_binaries?(parm)
end
return true if can_access?(parm)
false
end
def can_access_downloadsrcany?(parm)
return true if is_admin?
if parm.kind_of? Package
return true if can_source_access?(parm)
end
return true if can_access?(parm)
false
end
def has_local_role?( role, object )
if object.is_a?(Package) || object.is_a?(Project)
logger.debug "running local role package check: user #{self.login}, package #{object.name}, role '#{role.title}'"
rels = object.relationships.where(:role_id => role.id, :user_id => self.id)
return true if rels.exists?
rels = object.relationships.joins(:groups_users).where(:groups_users => { user_id: self.id }).where(:role_id => role.id)
return true if rels.exists?
return true if lookup_strategy.local_role_check(role, object)
end
if object.is_a? Package
return has_local_role?(role, object.project)
end
false
end
# local permission check
# if context is a package, check permissions in package, then if needed continue with project check
# if context is a project, check it, then if needed go down through all namespaces until hitting the root
# return false if none of the checks succeed
def has_local_permission?( perm_string, object )
roles = Role.ids_with_permission(perm_string)
return false unless roles
parent = nil
case object
when Package
logger.debug "running local permission check: user #{self.login}, package #{object.name}, permission '#{perm_string}'"
# check permission for given package
parent = object.project
when Project
logger.debug "running local permission check: user #{self.login}, project #{object.name}, permission '#{perm_string}'"
# check permission for given project
parent = object.parent
when nil
return has_global_permission?(perm_string)
else
return false
end
rel = object.relationships.where(:user_id => self.id).where('role_id in (?)', roles)
return true if rel.exists?
rel = object.relationships.joins(:groups_users).where(:groups_users => { user_id: self.id }).where('role_id in (?)', roles)
return true if rel.exists?
return true if lookup_strategy.local_permission_check(roles, object)
if parent
# check permission of parent project
logger.debug "permission not found, trying parent project '#{parent.name}'"
return has_local_permission?(perm_string, parent)
end
false
end
def involved_projects_ids
# just for maintainer for now.
role = Role.rolecache['maintainer']
### all projects where user is maintainer
projects = self.relationships.projects.where(role_id: role.id).pluck(:project_id)
# all projects where user is maintainer via a group
projects += Relationship.projects.where(role_id: role.id).joins(:groups_users).where(groups_users: { user_id: self.id }).pluck(:project_id)
projects.uniq
end
def involved_projects
# now filter the projects that are not visible
Project.where(id: involved_projects_ids)
end
# lists packages maintained by this user and are not in maintained projects
def involved_packages
# just for maintainer for now.
role = Role.rolecache['maintainer']
projects = involved_projects_ids
projects << -1 if projects.empty?
# all packages where user is maintainer
packages = self.relationships.where(role_id: role.id).joins(:package).where('packages.project_id not in (?)', projects).pluck(:package_id)
# all packages where user is maintainer via a group
packages += Relationship.packages.where(role_id: role.id).joins(:groups_users).where(groups_users: { user_id: self.id }).pluck(:package_id)
Package.where(id: packages).where('project_id not in (?)', projects)
end
# list packages owned by this user.
def owned_packages
owned = []
begin
Owner.search({}, self).each do |owner|
owned << [owner.package, owner.project]
end
rescue APIException => e # no attribute set
Rails.logger.debug "0wned #{e.inspect}"
end
owned
end
# lists reviews involving this user
def involved_reviews
open_reviews = BsRequest.collection(user: self.login, roles: %w(reviewer creator), reviewstates: %w(new), states: %w(review))
open_reviews.select do |review|
review['creator'] != login
end
end
# list requests involving this user
def declined_requests
BsRequest.collection(user: self.login, states: %w(declined), roles: %w(creator))
end
# list incoming requests involving this user
def incoming_requests
BsRequest.collection(user: self.login, states: %w(new), roles: %w(maintainer))
end
# list outgoing requests involving this user
def outgouing_requests
BsRequest.collection(user: login, states: %w(new review), roles: %w(creator))
end
# finds if the user have any request
def requests?
requests.count > 0
end
# list of all requests
def requests(search = nil)
BsRequest.collection(user: login, states: VALID_REQUEST_STATES, roles: %w(creator maintainer reviewer), search: search)
end
# lists running maintenance updates where this user is involved in
def involved_patchinfos
array = Array.new
rel = PackageIssue.joins(:issue).where(issues: { state: 'OPEN', owner_id: id})
rel = rel.joins('LEFT JOIN package_kinds ON package_kinds.package_id = package_issues.package_id')
ids = rel.where('package_kinds.kind="patchinfo"').pluck('distinct package_issues.package_id')
Package.where(id: ids).each do |p|
hash = {:package => {:project => p.project.name, :name => p.name}}
issues = Array.new
p.issues.each do |is|
i = {}
i[:name]= is.name
i[:tracker]= is.issue_tracker.name
i[:label]= is.label
i[:url]= is.url
i[:summary] = is.summary
i[:state] = is.state
i[:login] = is.owner.login if is.owner
i[:updated_at] = is.updated_at
issues << i
end
hash[:issues] = issues
array << hash
end
return array
end
def user_relevant_packages_for_status
role_id = Role.rolecache['maintainer'].id
# First fetch the project ids
projects_ids = self.involved_projects_ids
packages = Package.joins("LEFT OUTER JOIN relationships ON (relationships.package_id = packages.id AND relationships.role_id = #{role_id})")
# No maintainers
packages = packages.where([
'(relationships.user_id = ?) OR '\
'(relationships.user_id is null AND packages.project_id in (?) )', self.id, projects_ids])
packages.pluck(:id)
end
def to_s
self.login
end
def to_param
to_s
end
def nr_of_requests_that_need_work
Rails.cache.fetch("requests_for_#{login}", expires_in: 2.minutes) do
BsRequest.collection(user: login, states: %w(declined), roles: %w(creator)).count +
BsRequest.collection(user: login, states: %w(new), roles: %w(maintainer)).count +
BsRequest.collection(user: login, roles: %w(reviewer), reviewstates: %w(new), states: %w(review)).count
end
end
def self.fetch_field(person, field)
p = User.where(login: person).pluck(field)
p[0] || ''
end
def self.email_for_login(person)
fetch_field(person, :email)
end
def self.realname_for_login(person)
fetch_field(person, :realname)
end
def watched_project_names
Rails.cache.fetch(['watched_project_names', self]) do
Project.where(id: watched_projects.pluck(:project_id)).pluck(:name).sort
end
end
def add_watched_project(name)
watched_projects.create(project: Project.find_by_name!(name))
clear_watched_projects_cache
end
def remove_watched_project(name)
watched_projects.joins(:project).where(projects: { name: name }).delete_all
clear_watched_projects_cache
end
# Needed to clear cache even when user's updated_at timestamp did not change,
# aka. changes within the same second. Mainly an issue when in our test suite
def clear_watched_projects_cache
Rails.cache.delete(['watched_project_names', self])
end
def watches?(name)
watched_project_names.include? name
end
def update_globalroles(global_role_titles)
self.roles.replace(
Role.where(title: global_role_titles) + roles.where(global: false)
)
end
# returns the gravatar image as string or :none
def gravatar_image(size)
Rails.cache.fetch([self, 'home_face', size, Configuration.first]) do
if ::Configuration.gravatar
hash = Digest::MD5.hexdigest(self.email.downcase)
begin
content = ActiveXML.backend.load_external_url("http://www.gravatar.com/avatar/#{hash}?s=#{size}&d=wavatar")
content.force_encoding('ASCII-8BIT') if content
rescue ActiveXML::Transport::Error
# ignored
end
end
content || :none
end
end
def display_name
address = Mail::Address.new self.email
address.display_name = self.realname
address.format
end
def self.update_notifications(params, user = nil)
Event::Base.notification_events.each do |event_type|
values = params[event_type.to_s] || {}
event_type.receiver_roles.each do |role|
EventSubscription.update_subscription(event_type.to_s, role, user, !values[role].nil?)
end
end
end
def update_notifications(params)
User.update_notifications(params, self)
end
def mark_login!
update_attributes(last_logged_in_at: Time.now, login_failure_count: 0)
end
private
def can_modify_project_internal(project, ignoreLock)
# The ordering is important because of the lock status check
return false if !ignoreLock && project.is_locked?
return true if is_admin?
return true if has_global_permission? 'change_project'
return true if has_local_permission? 'change_project', project
return true if project.name == self.home_project_name # users tend to remove themself, allow to re-add them
false
end
# Hashes the given parameter by the selected hashing method. It uses the
# "password_salt" property's value to make the hashing more secure.
def hash_string(value)
crypt2index = { "md5crypt" => 1,
"sha256crypt" => 5 }
if password_hash_type == "md5"
Digest::MD5.hexdigest(value + password_salt)
elsif crypt2index.keys.include?(password_hash_type)
value.crypt("$#{crypt2index[password_hash_type]}$#{password_salt}$").split("$")[3]
else
'invalid'
end
end
cattr_accessor :lookup_strategy do
if Configuration.ldapgroup_enabled?
@@lstrategy = UserLdapStrategy.new
else
@@lstrategy = UserBasicStrategy.new
end
end
end