Class: RackEntraIdAuth::Middleware

Inherits:
Object
  • Object
show all
Defined in:
lib/rack_entra_id_auth/middleware.rb

Instance Method Summary collapse

Constructor Details

#initialize(app) ⇒ Middleware

Returns a new instance of Middleware.



5
6
7
# File 'lib/rack_entra_id_auth/middleware.rb', line 5

def initialize (app)
  @app = app
end

Instance Method Details

#call(env) ⇒ Object



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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/rack_entra_id_auth/middleware.rb', line 9

def call (env)
  request = Rack::Request.new(env)
  entra_id_request = EntraIdRequest.new(request)

  # SP initiated single sign-on request
  if entra_id_request.login?
    log(env, 'Redirecting login request to Entra ID single sign-on URL…')

    sso_url = entra_id_request.sso_url(
      { :RelayState => request.params['relay_state'] ||
                       RackEntraIdAuth.config. ||
                       entra_id_request.base_url })

    return found_redirect_response(
      sso_url,
      'Redirecting login request to Entra ID single sign-on URL')
  end

  # SP initiated logout/single logout request
  if entra_id_request.logout?
    log(env, 'Destroying session…')

    # destroy session in case single logout fails
    destroy_session(request.session)

    relay_state_url = request.params['relay_state'] ||
                      RackEntraIdAuth.config.logout_relay_state_url ||
                      entra_id_request.base_url

    slo_url = entra_id_request.slo_url({ :RelayState => relay_state_url })

    if request.params['skip_single_logout'].blank? and
       !RackEntraIdAuth.config.skip_single_logout and
       slo_url.present?

      log(env, 'Redirecting logout request to Entra ID single logout URL…')

      return found_redirect_response(
        slo_url,
        'Redirecting logout request to Entra ID single logout URL')
    end

    log(env, 'Skipping single logout because of skip_single_logout query parameter…') if request.params['skip_single_logout'].present?
    log(env, 'Skipping single logout because of skip_single_logout configuration setting…') if RackEntraIdAuth.config.skip_single_logout
    log(env, 'Skipping single logout because no Entra ID single logout URL was found…') if slo_url.blank?

    log(env, 'Redirecting to relay state URL…')

    return found_redirect_response(relay_state_url)
  end

  # SP initiatied single sign-on response
  if entra_id_request.
    log(env, 'Received single login response…')

    auth_response = entra_id_request.saml_auth_response()

    if !auth_response.is_valid?
      log(env, "Invalid single login reponse from Entra ID: #{auth_response.errors.first}")

      return internal_server_error_response("Invalid single login reponse from Entra ID: #{auth_response.errors.first}")
    end

    if !auth_response.success?
      log(env, 'Unsuccessful single single reponse from Entra ID.')

      return internal_server_error_response('Unsuccessful single login reponse from Entra ID.')
    end

    log(env, 'Initializing session and redirecting to relay state URL…')

    # initialize the session with the response's SAML attributes
    request.session[RackEntraIdAuth.config.session_key] = RackEntraIdAuth.config.session_value_proc.call(auth_response.attributes.all)

    return found_redirect_response(
             entra_id_request.relay_state_url,
             'Received single login response, redirecting to relay state URL')
  end

  # IdP initiatied single logout request
  if entra_id_request.logout_request? and !RackEntraIdAuth.config.skip_single_logout
    log(env, 'Received single logout request…')

    logout_request = entra_id_request.saml_logout_request()

    if !logout_request.is_valid?
      log(env, "Invalid single logout request from Entra ID: #{logout_request.errors.first}")

      return internal_server_error_response("Invalid single logout request from Entra ID: #{logout_request.errors.first}")
    end

    log(env, 'Destroying session and sending logout response to Entra ID…')

    # destroy the session
    destroy_session(request.session)

    response_url = entra_id_request.slo_response_url(
      request_id: logout_request.id,
      logout_message: nil,
      params: {
        :RelayState => entra_id_request.relay_state_url
      },
      logout_status_code: nil)

    return found_redirect_response(
             response_url,
             'Received single logout request, redirecting to Entra ID')
  end

  # SP initiated single logout response
  if entra_id_request.logout_response?
    log(env, 'Received single logout response…')

    logout_response = entra_id_request.saml_logout_response()

    if !logout_response.validate
      log(env, "Invalid single logout reponse from Entra ID: #{logout_response.errors.first}")

      return internal_server_error_response("Invalid single logout reponse from Entra ID: #{logout_response.errors.first}")
    end

    if !logout_response.success?
      log(env, 'Unsuccessful single logout reponse from Entra ID.')

      return internal_server_error_response('Unsuccessful single logout reponse from Entra ID.')
    end

    log(env, 'Destroying session and redirecting to relay state URL…')

    # session should already be destroyed from SP initiated logout/single
    # logout request, but just to be safe…
    destroy_session(request.session)

    return found_redirect_response(
             entra_id_request.relay_state_url,
             'Received single logout response, redirecting to relay state URL')
  end

  response = @app.call(env)

  # Authenticate 401s
  if response[0] == 401
    log(env, 'Intercepted 401 Unauthorized response, redirecting to Entra ID single sign-on URL…')

    return found_redirect_response(
             entra_id_request.sso_url(:RelayState => request.url),
             'Intercepted 401 Unauthorized response, redirecting to Entra ID single sign-on URL')
  end

  response
end