main
 1# frozen_string_literal: true
 2
 3class Client < ApplicationRecord
 4  RESPONSE_TYPES = %w[code token].freeze
 5  audited
 6  has_secure_password
 7  has_many :authorizations, dependent: :delete_all
 8  attribute :redirect_uris, :string, array: true
 9  enum token_endpoint_auth_method: {
10    client_secret_basic: 0,
11    client_secret_post: 1,
12    client_secret_none: 2,
13  }
14
15  validates :redirect_uris, presence: true
16  validates :jwks_uri, format: { with: URI_REGEX }, allow_blank: true
17  validates :logo_uri, format: { with: URI_REGEX }, allow_blank: true
18  validates :name, presence: true
19  validates_each :redirect_uris do |record, _attr, value|
20    invalid_uri = Array(value).find { |x| !x.match?(URI_REGEX) }
21    record.errors[:redirect_uris] << 'is invalid.' if invalid_uri
22  end
23
24  after_initialize do
25    self.password = SecureRandom.base58(24) unless password_digest
26  end
27
28  def grant_types
29    [
30      :authorization_code,
31      :refresh_token,
32      :client_credentials,
33      :password,
34      'urn:ietf:params:oauth:grant-type:saml2-bearer'
35    ]
36  end
37
38  def access_token
39    transaction do
40      Token
41        .active.where(subject: self, audience: self)
42        .update_all(revoked_at: Time.current)
43      Token.create!(subject: self, audience: self, token_type: :access)
44    end
45  end
46
47  def revoke(token)
48    token.revoke! if token.issued_to?(self)
49  end
50
51  def valid_redirect_uri?(redirect_uri)
52    redirect_uris.include? redirect_uri
53  end
54
55  def valid_response_type?(response_type)
56    RESPONSE_TYPES.include?(response_type)
57  end
58
59  def redirect_url_for(user, oauth)
60    sha256 = oauth[:code_challenge_method] == 'S256'
61    authorization = authorizations.create!(
62      user: user,
63      challenge: oauth[:code_challenge],
64      challenge_method: sha256 ? :sha256 : :plain
65    )
66
67    if oauth[:response_type] == 'code'
68      redirect_url(code: authorization.code, state: oauth[:state]) do
69        oauth[:redirect_uri]
70      end
71    elsif oauth[:response_type] == 'token'
72      access_token, = authorization.issue_tokens_to(
73        self, token_types: [:access]
74      )
75      redirect_url(
76        access_token: access_token.to_jwt,
77        token_type: 'Bearer',
78        expires_in: 5.minutes.to_i,
79        scope: :admin,
80        state: oauth[:state]
81      ) do
82        oauth[:redirect_uri]
83      end
84    else
85      redirect_url(error: 'unsupported_response_type', state: state)
86    end
87  end
88
89  def redirect_url(fragments = {})
90    redirect_uri = block_given? ? yield : redirect_uris[0]
91    return unless valid_redirect_uri?(redirect_uri)
92
93    URI.parse(
94      "#{redirect_uri}#" + fragments.map do |(key, value)|
95        "#{key}=#{value}" if value.present?
96      end.compact.join("&")
97    ).to_s
98  end
99end