Module: Legion::Gaia::Channels::Teams::BotFrameworkAuth

Extended by:
Logging::Helper
Defined in:
lib/legion/gaia/channels/teams/bot_framework_auth.rb

Constant Summary collapse

OPENID_METADATA_URL =
'https://login.botframework.com/v1/.well-known/openidconfiguration'
BOT_FRAMEWORK_ISSUER =
'https://api.botframework.com'
EMULATOR_ISSUER =
'https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/'
JWKS_CACHE_TTL =
3600

Class Method Summary collapse

Class Method Details

.check_expiry(payload) ⇒ Object



84
85
86
87
88
89
# File 'lib/legion/gaia/channels/teams/bot_framework_auth.rb', line 84

def check_expiry(payload)
  now = Time.now.to_i
  return { valid: false, error: :token_expired } if payload['exp'] && payload['exp'].to_i < now

  { valid: false, error: :token_not_yet_valid }
end

.check_issuer(payload, _allow_emulator) ⇒ Object



98
99
100
# File 'lib/legion/gaia/channels/teams/bot_framework_auth.rb', line 98

def check_issuer(payload, _allow_emulator)
  { valid: false, error: :invalid_issuer, issuer: payload['iss'] }
end

.decode_jwt_segment(segment) ⇒ Object



66
67
68
69
70
71
72
73
74
# File 'lib/legion/gaia/channels/teams/bot_framework_auth.rb', line 66

def decode_jwt_segment(segment)
  remainder = segment.length % 4
  padded = remainder.zero? ? segment : segment + ('=' * (4 - remainder))
  decoded = Base64.urlsafe_decode64(padded)
  ::JSON.parse(decoded)
rescue StandardError => e
  handle_exception(e, level: :debug, operation: 'gaia.channels.teams.bot_framework_auth.decode_jwt_segment')
  nil
end

.extract_identity(activity) ⇒ Object



55
56
57
58
59
60
61
62
63
64
# File 'lib/legion/gaia/channels/teams/bot_framework_auth.rb', line 55

def extract_identity(activity)
  from = activity['from'] || activity[:from] || {}
  {
    aad_object_id: from['aadObjectId'] || from[:aadObjectId],
    user_id: from['id'] || from[:id],
    user_name: from['name'] || from[:name],
    tenant_id: activity.dig('channelData', 'tenant', 'id') ||
      activity.dig(:channelData, :tenant, :id)
  }
end

.issuer_valid?(payload, allow_emulator) ⇒ Boolean

Returns:

  • (Boolean)


91
92
93
94
95
96
# File 'lib/legion/gaia/channels/teams/bot_framework_auth.rb', line 91

def issuer_valid?(payload, allow_emulator)
  issuer = payload['iss']
  valid_issuers = [BOT_FRAMEWORK_ISSUER]
  valid_issuers << EMULATOR_ISSUER if allow_emulator
  valid_issuers.any? { |i| issuer&.start_with?(i) || issuer == i }
end

.token_time_valid?(payload) ⇒ Boolean

Returns:

  • (Boolean)


76
77
78
79
80
81
82
# File 'lib/legion/gaia/channels/teams/bot_framework_auth.rb', line 76

def token_time_valid?(payload)
  now = Time.now.to_i
  return false if payload['exp'] && payload['exp'].to_i < now
  return false if payload['nbf'] && payload['nbf'].to_i > now + 300

  true
end

.validate_claims(payload, app_id:, allow_emulator: false) ⇒ Object



47
48
49
50
51
52
53
# File 'lib/legion/gaia/channels/teams/bot_framework_auth.rb', line 47

def validate_claims(payload, app_id:, allow_emulator: false)
  return check_expiry(payload) unless token_time_valid?(payload)
  return check_issuer(payload, allow_emulator) unless issuer_valid?(payload, allow_emulator)
  return { valid: false, error: :invalid_audience, audience: payload['aud'] } unless payload['aud'] == app_id

  { valid: true }
end

.validate_token(token, app_id:, allow_emulator: false) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/legion/gaia/channels/teams/bot_framework_auth.rb', line 23

def validate_token(token, app_id:, allow_emulator: false)
  return { valid: false, error: :missing_token } if token.nil? || token.empty?

  parts = token.split('.')
  return { valid: false, error: :malformed_jwt } unless parts.size == 3

  header = decode_jwt_segment(parts[0])
  payload = decode_jwt_segment(parts[1])

  return { valid: false, error: :decode_failed } unless header && payload

  validation = validate_claims(payload, app_id: app_id, allow_emulator: allow_emulator)
  return validation unless validation[:valid]

  {
    valid: true,
    claims: payload,
    entra_oid: payload['oid'],
    app_id: payload['appid'] || payload['azp'],
    tenant_id: payload['tid'],
    service_url: payload['serviceurl']
  }
end