-
Notifications
You must be signed in to change notification settings - Fork 31
/
Copy pathAuthorizationController.cs
302 lines (252 loc) · 9.5 KB
/
AuthorizationController.cs
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
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
//
// Copyright (C) 2018 Authlete, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific
// language governing permissions and limitations under the
// License.
//
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Authlete.Api;
using Authlete.Dto;
using Authlete.Handler;
using Authlete.Types;
using Authlete.Util;
using Authlete.Web;
using AuthorizationServer.Models;
using AuthorizationServer.Spi;
using AuthorizationServer.Util;
namespace AuthorizationServer.Controllers
{
/// <summary>
/// An implementation of OAuth 2.0
/// <a href="http://tools.ietf.org/html/rfc6749#section-3.1">authorization
/// endpoint</a> with
/// <a href="http://openid.net/connect/">OpenID Connect</a>
/// support.
/// </summary>
[Route("api/[controller]")]
public class AuthorizationController : BaseController
{
// View name of the authorization page. See "View discovery"
// of "Views in ASP.NET Core MVC" for details.
const string VIEW_NAME = "Page";
readonly IViewEngine _viewEngine;
public AuthorizationController(
IAuthleteApi api, ICompositeViewEngine viewEngine)
: base(api)
{
_viewEngine = viewEngine;
}
/// <summary>
/// An authorization endpoint for HTTP GET method.
/// </summary>
///
/// <remarks>
/// <para>
/// 3.1. Authorization Endpoint (RFC 6749) says that the
/// authorization endpoint MUST support GET method.
/// </para>
/// </remarks>
[HttpGet]
public async Task<HttpResponseMessage> Get()
{
// Query parameters.
string parameters = Request.QueryString.ToString();
return await Process(parameters);
}
/// <summary>
/// An authorization endpoint for HTTP POST method.
/// </summary>
///
/// <remarks>
/// <para>
/// 3.1. Authorization Endpoint (RFC 6749) says that the
/// authorization endpoint MAY support POST method.
/// </para>
///
/// <para>
/// In addition, 3.1.2.1. Authentication Request (OpenID
/// Connect Core 1.0) says that the authorization endpoint
/// MUST support POST method.
/// </para>
/// </remarks>
[HttpPost]
[Consumes("application/x-www-form-urlencoded")]
public async Task<HttpResponseMessage> Post()
{
// Form parameters.
string parameters = await ReadRequestBodyAsString();
return await Process(parameters);
}
async Task<HttpResponseMessage> Process(string parameters)
{
// Call Authlete's /api/auth/authorization API.
AuthorizationResponse response =
await CallAuthorizationApi(parameters);
// 'action' in the response denotes the next action
// which this authorization endpoint implementation
// should take.
AuthorizationAction action = response.Action;
// Dispatch according to the action.
switch (action)
{
case AuthorizationAction.INTERACTION:
// Process the authorization request with
// user interaction.
return await HandleInteraction(response);
case AuthorizationAction.NO_INTERACTION:
// Process the authorization request without
// user interaction. The flow reaches here
// only when the authorization request contains
// 'prompt=none'.
return await HandleNoInteraction(response);
default:
// Handle other error cases here.
return HandleError(response);
}
}
async Task<AuthorizationResponse> CallAuthorizationApi(
string parameters)
{
// Create a request for Authlete's
// /api/auth/authorization API.
var request = new AuthorizationRequest
{
Parameters = parameters
};
// Call Authlete's /api/auth/authorization API.
return await API.Authorization(request);
}
async Task<HttpResponseMessage> HandleNoInteraction(
AuthorizationResponse response)
{
// Make NoInteractionHandler handle the case of
// 'prompt=none'. An implementation of the
// INoInteractionHandlerSpi interface needs to be given
// to the constructor of NoInteractionHandler.
return await new NoInteractionHandler(
API, new NoInteractionHandlerSpiImpl(this))
.Handle(response);
}
HttpResponseMessage HandleError(AuthorizationResponse response)
{
// Make AuthorizationRequestErrorHandler handle the
// error case.
return new AuthorizationRequestErrorHandler()
.Handle(response);
}
async Task<HttpResponseMessage> HandleInteraction(
AuthorizationResponse response)
{
// Store some variables into TempData so that they can
// be referred to in AuthorizationDecisionController.
var data = new UserTData(TempData);
data.Set( "ticket", response.Ticket);
data.SetObject("claimNames", response.Claims);
data.SetObject("claimLocales", response.ClaimsLocales);
// Clear user information in TempData if necessary.
ClearUserDataIfNecessary(response, data);
// Prepare a model object which is needed to render
// the authorization page.
var model = new AuthorizationPageModel(
response, data.GetUserEntity());
// Render the authorization page manually.
string html = await Render(VIEW_NAME, model);
// Return "200 OK" with "text/html".
return ResponseUtility.OkHtml(html);
}
void ClearUserDataIfNecessary(
AuthorizationResponse response, UserTData data)
{
// Get the user information from TempData.
var entity = data.GetUserEntity();
// If user information does not exist in TempData.
if (entity == null)
{
// Nothing to clear.
return;
}
// If 'login' is required (= if the "prompt" parameter
// of the authorization request includes "login").
if (IsLoginRequired(response))
{
// Even if a user has already logged in, the user
// needs to be re-authenticated. This simple
// implementation forces the user to log out here.
data.RemoveUserTData();
return;
}
// If the max authentication age has been exceeded.
if (IsMaxAgeExceeded(response, data))
{
// Much time has elapsed since the last login, so
// re-authentication is needed. This simple
// implementation forces the user to log out here.
data.RemoveUserTData();
return;
}
// No need to clear user data.
}
bool IsLoginRequired(AuthorizationResponse response)
{
// If the authorization request does not have
// a "prompt" request parameter.
if (response.Prompts == null)
{
// 'login' is not required.
return false;
}
// For each value in the "prompt" request parameter.
foreach (Prompt prompt in response.Prompts)
{
if (prompt == Prompt.LOGIN)
{
// 'login' is required.
return true;
}
}
// 'login' is not required.
return false;
}
bool IsMaxAgeExceeded(
AuthorizationResponse response, UserTData data)
{
if (response.MaxAge <= 0)
{
// Don't have to care about the maximum
// authentication age.
return false;
}
// Calculate the number of seconds that have elapsed
// since the last login.
long age =
TimeUtility.CurrentTimeSeconds()
- data.GetUserAuthenticatedAt();
if (age <= response.MaxAge)
{
// The max age is not exceeded yet.
return false;
}
// The max age has been exceeded.
return true;
}
async Task<string> Render(string viewName, object model)
{
// Render the view manually.
return await new Renderer(this, _viewEngine)
.Render(viewName, model);
}
}
}