summaryrefslogtreecommitdiff
path: root/app/models/client.rb
blob: 071667aa2fa8930d51aeb48137e014b1af99d85b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# frozen_string_literal: true

class Client < ApplicationRecord
  RESPONSE_TYPES = %w[code token].freeze
  audited
  has_secure_password
  has_many :authorizations, dependent: :delete_all
  attribute :redirect_uris, :string, array: true
  enum token_endpoint_auth_method: {
    client_secret_basic: 0,
    client_secret_post: 1,
    client_secret_none: 2,
  }

  validates :redirect_uris, presence: true
  validates :jwks_uri, format: { with: URI_REGEX }, allow_blank: true
  validates :logo_uri, format: { with: URI_REGEX }, allow_blank: true
  validates :name, presence: true
  validates_each :redirect_uris do |record, _attr, value|
    invalid_uri = Array(value).find { |x| !x.match?(URI_REGEX) }
    record.errors[:redirect_uris] << 'is invalid.' if invalid_uri
  end

  after_initialize do
    self.password = SecureRandom.base58(24) unless password_digest
  end

  def grant_types
    [
      :authorization_code,
      :refresh_token,
      :client_credentials,
      :password,
      'urn:ietf:params:oauth:grant-type:saml2-bearer'
    ]
  end

  def access_token
    transaction do
      Token
        .active.where(subject: self, audience: self)
        .update_all(revoked_at: Time.current)
      Token.create!(subject: self, audience: self, token_type: :access)
    end
  end

  def revoke(token)
    token.revoke! if token.issued_to?(self)
  end

  def valid_redirect_uri?(redirect_uri)
    redirect_uris.include? redirect_uri
  end

  def valid_response_type?(response_type)
    RESPONSE_TYPES.include?(response_type)
  end

  def redirect_url_for(user, oauth)
    sha256 = oauth[:code_challenge_method] == 'S256'
    authorization = authorizations.create!(
      user: user,
      challenge: oauth[:code_challenge],
      challenge_method: sha256 ? :sha256 : :plain
    )

    if oauth[:response_type] == 'code'
      redirect_url(code: authorization.code, state: oauth[:state]) do
        oauth[:redirect_uri]
      end
    elsif oauth[:response_type] == 'token'
      access_token, = authorization.issue_tokens_to(
        self, token_types: [:access]
      )
      redirect_url(
        access_token: access_token.to_jwt,
        token_type: 'Bearer',
        expires_in: 5.minutes.to_i,
        scope: :admin,
        state: oauth[:state]
      ) do
        oauth[:redirect_uri]
      end
    else
      redirect_url(error: 'unsupported_response_type', state: state)
    end
  end

  def redirect_url(fragments = {})
    redirect_uri = block_given? ? yield : redirect_uris[0]
    return unless valid_redirect_uri?(redirect_uri)

    URI.parse(
      "#{redirect_uri}#" + fragments.map do |(key, value)|
        "#{key}=#{value}" if value.present?
      end.compact.join("&")
    ).to_s
  end
end