Class: Gitlab::Auth::Identity

Inherits:
Object
  • Object
show all
Defined in:
lib/gitlab/auth/identity.rb

Overview

Identity class represents identity which we want to use in authorization policies.

It decides if an identity is a single or composite identity and finds identity scope.

Constant Summary collapse

COMPOSITE_IDENTITY_USERS_KEY =
'composite_identities'
COMPOSITE_IDENTITY_KEY_FORMAT =
'user:%s:composite_identity'
COMPOSITE_IDENTITY_SIDEKIQ_ARG =

Sidekiq Composite Identity

'sqci'
IdentityError =
Class.new(StandardError)
IdentityLinkMismatchError =
Class.new(IdentityError)
UnexpectedIdentityError =
Class.new(IdentityError)
TooManyIdentitiesLinkedError =
Class.new(IdentityError)
MissingCompositeIdentityError =
Class.new(::Gitlab::Access::AccessDeniedError)
MissingServiceAccountError =
Class.new(::Gitlab::Access::AccessDeniedError)

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(user, store: ::Gitlab::SafeRequestStore) ⇒ Identity

Returns a new instance of Identity.



107
108
109
110
111
112
# File 'lib/gitlab/auth/identity.rb', line 107

def initialize(user, store: ::Gitlab::SafeRequestStore)
  raise UnexpectedIdentityError unless user.is_a?(::User)

  @user = user
  @request_store = store
end

Class Method Details

.currently_linkedObject



68
69
70
71
72
73
74
75
76
77
78
# File 'lib/gitlab/auth/identity.rb', line 68

def self.currently_linked
  user = ::Gitlab::SafeRequestStore
    .store[COMPOSITE_IDENTITY_USERS_KEY]
    .to_a.first

  return unless user.present?

  identity = new(user)

  block_given? ? yield(identity) : identity
end

.fabricate(user) ⇒ Object



80
81
82
# File 'lib/gitlab/auth/identity.rb', line 80

def self.fabricate(user)
  new(user) if user.is_a?(::User)
end

.find_primary_user_by_scoped_user_id(scoped_user_id, store: ::Gitlab::SafeRequestStore) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/gitlab/auth/identity.rb', line 84

def self.find_primary_user_by_scoped_user_id(scoped_user_id, store: ::Gitlab::SafeRequestStore)
  return unless scoped_user_id

  # Get all composite identities from the store
  composite_identities = store.store[COMPOSITE_IDENTITY_USERS_KEY] || Set.new

  # Check each composite identity to find the one with matching scoped user
  composite_identities.find do |primary_user|
    identity_key = format(COMPOSITE_IDENTITY_KEY_FORMAT, primary_user.id)
    scoped_user = store.store[identity_key]

    scoped_user&.id == scoped_user_id
  end
end

.invert_composite_identity(current_user) ⇒ Object



99
100
101
102
103
104
105
# File 'lib/gitlab/auth/identity.rb', line 99

def self.invert_composite_identity(current_user)
  return unless current_user

  primary_user = Gitlab::Auth::Identity.find_primary_user_by_scoped_user_id(current_user.id)

  primary_user || current_user
end


29
30
31
32
33
# File 'lib/gitlab/auth/identity.rb', line 29

def self.link_from_job(job)
  fabricate(job.user).tap do |identity|
    identity.link!(job.scoped_user) if identity&.composite?
  end
end

TODO: why is this called 3 times in doorkeeper_access_spec.rb specs?



23
24
25
26
27
# File 'lib/gitlab/auth/identity.rb', line 23

def self.link_from_oauth_token(oauth_token)
  fabricate(oauth_token.user).tap do |identity|
    identity.link!(oauth_token.scope_user) if identity&.composite?
  end
end


43
44
45
46
47
# File 'lib/gitlab/auth/identity.rb', line 43

def self.link_from_scoped_user(user, scoped_user)
  ::Gitlab::Auth::Identity.fabricate(user).tap do |identity|
    identity.link!(scoped_user) if identity&.composite?
  end
end


35
36
37
38
39
40
41
# File 'lib/gitlab/auth/identity.rb', line 35

def self.link_from_scoped_user_id(user, scoped_user_id)
  scoped_user = ::User.find_by_id(scoped_user_id)

  return unless scoped_user

  ::Gitlab::Auth::Identity.link_from_scoped_user(user, scoped_user)
end


49
50
51
52
53
54
55
# File 'lib/gitlab/auth/identity.rb', line 49

def self.link_from_web_request(service_account:, scoped_user:)
  raise MissingServiceAccountError, 'service account is required' unless 

  fabricate().tap do |identity|
    identity.link!(scoped_user) if identity&.composite?
  end
end

.sidekiq_restore!(job) ⇒ Object

Raises:



57
58
59
60
61
62
63
64
65
66
# File 'lib/gitlab/auth/identity.rb', line 57

def self.sidekiq_restore!(job)
  ids = Array(job[COMPOSITE_IDENTITY_SIDEKIQ_ARG])

  return if ids.empty?
  raise IdentityError, 'unexpected number of identities in Sidekiq job' unless ids.size == 2

  ::Gitlab::Auth::Identity
    .new(::User.find(ids.first))
    .link!(::User.find(ids.second))
end

Instance Method Details

#composite?Boolean

Returns:

  • (Boolean)


114
115
116
# File 'lib/gitlab/auth/identity.rb', line 114

def composite?
  @user.composite_identity_enforced?
end

#link!(scope_user) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/gitlab/auth/identity.rb', line 122

def link!(scope_user)
  return self unless scope_user

  ##
  # TODO: consider extracting linking to ::Gitlab::Auth::Identities::Link#create!
  #
  validate_link!(scope_user)
  store_identity_link!(scope_user)
  append_log!(scope_user)

  self
end

#linked?Boolean

Returns:

  • (Boolean)


135
136
137
# File 'lib/gitlab/auth/identity.rb', line 135

def linked?
  @request_store.exist?(store_key)
end

#primary_userObject



153
154
155
# File 'lib/gitlab/auth/identity.rb', line 153

def primary_user
  @user
end

#scoped_userObject



147
148
149
150
151
# File 'lib/gitlab/auth/identity.rb', line 147

def scoped_user
  @request_store.fetch(store_key) do
    raise MissingCompositeIdentityError, 'composite identity missing'
  end
end

#sidekiq_link!(job) ⇒ Object



118
119
120
# File 'lib/gitlab/auth/identity.rb', line 118

def sidekiq_link!(job)
  job[COMPOSITE_IDENTITY_SIDEKIQ_ARG] = [primary_user_id, scoped_user_id]
end

#valid?Boolean

Returns:

  • (Boolean)


139
140
141
142
143
144
145
# File 'lib/gitlab/auth/identity.rb', line 139

def valid?
  return true unless composite?

  return false unless linked?

  !scoped_user.composite_identity_enforced?
end