From 5f12aab0b1a431c792a88b049c72fd13be36e47a Mon Sep 17 00:00:00 2001 From: Cameron Purdy <699204+cpurdy@users.noreply.github.com> Date: Mon, 18 Nov 2024 10:13:33 -0500 Subject: [PATCH] Design updates for Authenticator and Broker integration (WIP) --- lib_xenia/src/main/x/xenia/Catalog.x | 34 ++- lib_xenia/src/main/x/xenia/ChainBundle.x | 23 +- lib_xenia/src/main/x/xenia/CookieBroker.x | 253 ++++++++++++++++++++- lib_xenia/src/main/x/xenia/Dispatcher.x | 38 +--- lib_xenia/src/main/x/xenia/HttpHandler.x | 4 +- lib_xenia/src/main/x/xenia/SystemService.x | 244 -------------------- 6 files changed, 290 insertions(+), 306 deletions(-) delete mode 100644 lib_xenia/src/main/x/xenia/SystemService.x diff --git a/lib_xenia/src/main/x/xenia/Catalog.x b/lib_xenia/src/main/x/xenia/Catalog.x index 2eb85827c..3341ea918 100644 --- a/lib_xenia/src/main/x/xenia/Catalog.x +++ b/lib_xenia/src/main/x/xenia/Catalog.x @@ -8,7 +8,7 @@ import sec.Permission; import web.*; -import WebService.Constructor; +import WebService.Constructor as WSConstructor; /** * The catalog of WebApp endpoints. @@ -72,7 +72,7 @@ const Catalog(WebApp webApp, String systemPath, WebServiceInfo[] services, Class */ static const WebServiceInfo(Int id, String path, - Constructor constructor, + WSConstructor constructor, EndpointInfo[] endpoints, EndpointInfo? defaultGet, MethodInfo[] interceptors, @@ -317,7 +317,7 @@ const Catalog(WebApp webApp, String systemPath, WebServiceInfo[] services, Class * @param extras (optional) a map of WebService classes for processing requests for * corresponding paths */ - static Catalog buildCatalog(WebApp app, Map, Constructor> extras = []) { + static Catalog buildCatalog(WebApp app, Map, WSConstructor> extras = []) { ClassInfo[] classInfos = new ClassInfo[]; Class[] sessionMixins = new Class[]; @@ -326,7 +326,7 @@ const Catalog(WebApp webApp, String systemPath, WebServiceInfo[] services, Class // collect ClassInfos for "extras"; this should be done first to account for services // without default constructors, which those extra services possibly are if (!extras.empty) { - for ((Class clz, Constructor constructor) : extras) { + for ((Class clz, WSConstructor constructor) : extras) { if (AnnotationTemplate webServiceAnno := clz.annotatedBy(WebService)) { String path = extractPath(webServiceAnno, declaredPaths); classInfos += new ClassInfo(path, clz, constructor); @@ -340,29 +340,27 @@ const Catalog(WebApp webApp, String systemPath, WebServiceInfo[] services, Class // collect the ClassInfos for standard WebServices and Session mixins scanClasses(app.classes, classInfos, sessionMixins, declaredPaths); - // compute the system service name and add the system service info - String systemPath = DefaultSystemPath; - for (Int i = 0; declaredPaths.contains(systemPath); i++) { - systemPath = $"{DefaultSystemPath}_{i}"; - } - classInfos += new ClassInfo(systemPath, SystemService, - SystemService.PublicType.defaultConstructor() ?: assert); +// TODO CP add any endpoints on the broker(s) and authenticator(s) +// // compute the system service name and add the system service info +// String systemPath = DefaultSystemPath; +// for (Int i = 0; declaredPaths.contains(systemPath); i++) { +// systemPath = $"{DefaultSystemPath}_{i}"; +// } +// classInfos += new ClassInfo(systemPath, SystemService, +// SystemService.PublicType.defaultConstructor() ?: assert); // sort the ClassInfos based on their paths (SystemService goes first) - classInfos.sorted((ci1, ci2) -> - ci1.path == systemPath ? Lesser : (ci1.path <=> ci2.path).reversed, inPlace=True); + classInfos.sorted((ci1, ci2) -> (ci1.path <=> ci2.path).reversed, inPlace=True); - // now collect all endpoints + // collect all of the endpoints into a Catalog WebServiceInfo[] webServiceInfos = collectEndpoints(app, classInfos); - assert webServiceInfos[0].path == systemPath; - return new Catalog(app, systemPath, webServiceInfos, sessionMixins); } /** * WebService info collected during the scan phase. */ - private static const ClassInfo(String path, Class clz, Constructor constructor); + private static const ClassInfo(String path, Class clz, WSConstructor constructor); /** * Scan all the specified classes for WebServices and add the corresponding information @@ -379,7 +377,7 @@ const Catalog(WebApp webApp, String systemPath, WebServiceInfo[] services, Class assert child.is(Class); Type serviceType = child.PublicType; - if (Constructor constructor := serviceType.defaultConstructor()) { + if (WSConstructor constructor := serviceType.defaultConstructor()) { String path = extractPath(webServiceAnno, declaredPaths); classInfos += new ClassInfo(path, child, constructor); diff --git a/lib_xenia/src/main/x/xenia/ChainBundle.x b/lib_xenia/src/main/x/xenia/ChainBundle.x index 05e3f47a5..e43a94521 100644 --- a/lib_xenia/src/main/x/xenia/ChainBundle.x +++ b/lib_xenia/src/main/x/xenia/ChainBundle.x @@ -42,13 +42,13 @@ service ChainBundle { * @param index the index of this ChainBundle in the `BundlePool` */ construct(Catalog catalog, Int index) { - this.catalog = catalog; - this.index = index; - - registry = catalog.webApp.registry_; - services = new WebService?[catalog.serviceCount]; - chains = new Handler?[catalog.endpointCount]; - errorHandlers = new ErrorHandler?[catalog.serviceCount]; + this.catalog = catalog; + this.index = index; + this.authenticator = catalog.webApp.authenticator.duplicate(); + this.registry = catalog.webApp.registry_; + this.services = new WebService?[catalog.serviceCount]; + this.chains = new Handler?[catalog.endpointCount]; + this.errorHandlers = new ErrorHandler?[catalog.serviceCount]; } /** @@ -69,6 +69,13 @@ service ChainBundle { */ public/private Catalog catalog; + /** + * The application's [Authenticator]; each ChainBundle duplicates the original `Authenticator` + * to avoid contention on a single `Authenticator` in case it is implemented without concurrency + * and has high-latency (e.g. database) operations. + */ + private Authenticator authenticator; + /** * The index of this bundle in the BundlePool. */ @@ -97,7 +104,7 @@ service ChainBundle { /** * Obtain a call chain for the specified endpoint. */ - Handler ensureCallChain(EndpointInfo endpoint, Authenticator authenticator) { + Handler ensureCallChain(EndpointInfo endpoint) { Handler? handle = chains[endpoint.id]; if (handle != Null) { return handle; diff --git a/lib_xenia/src/main/x/xenia/CookieBroker.x b/lib_xenia/src/main/x/xenia/CookieBroker.x index e0edeaf5b..047a7e85c 100644 --- a/lib_xenia/src/main/x/xenia/CookieBroker.x +++ b/lib_xenia/src/main/x/xenia/CookieBroker.x @@ -2,16 +2,17 @@ import HttpServer.RequestInfo; import SessionCookie.CookieId; import web.HttpStatus; +import web.WebService; import web.sessions.Broker; - /** * The `CookieBroker` is a traditional [Session] [Broker] that uses cookies and HTTP redirects to * establish and verify HTTP sessions for browser-based (and other cookie- and redirect-friendly) * HTTP clients. */ +@WebService("/.well-known/xverify-cookies") service CookieBroker implements Broker { @@ -32,7 +33,7 @@ service CookieBroker this.consentCookieName = that.consentCookieName; } - // ----- Broker interface ---------------------------------------------------------------------- + // ----- properties ---------------------------------------------------------------------------- /** * The session manager. @@ -67,6 +68,10 @@ service CookieBroker // TODO return False; + // ----- WebService endpoints ------------------------------------------------------------------ + + // ----- internal ------------------------------------------------------------------------------ + // // handle the error result (no session returned) // if (result.is(HttpStatus)) { // response = new SimpleResponse(result); @@ -635,3 +640,247 @@ service CookieBroker ); } } +//import HttpServer.RequestInfo; +//import SessionCookie.CookieId; +//import SessionImpl.Match_; +// +//import web.Header; +//import web.HttpStatus; +// +//import web.responses.SimpleResponse; +// +// +///** +// * The SystemService provides end points for "system" functionality (like verifying HTTPS/TLS is +// * enabled, handling some forms of authentication, etc.), and is automatically added to every +// * Xenia-hosted web application. +// */ +//@WebService("/xverify") +//service SystemService { +// // ----- properties ---------------------------------------------------------------------------- +// +// /** +// * The cached `Catalog`. +// */ +// @Lazy Catalog catalog.calc() { +// assert val catalog := webApp.registry_.getResource("catalog"); +// return catalog.as(Catalog); +// } +// +// /** +// * The cached `SessionManager`. +// */ +// @Lazy SessionManager sessionManager.calc() { +// assert val mgr := webApp.registry_.getResource("sessionManager"); +// return mgr.as(SessionManager); +// } +// +// +// // ----- HTTP endpoints ------------------------------------------------------------------------ +// +// /** +// * Handle a system service call. This is the "endpoint" for the service. +// * +// * @param uri the URI string that identifies the system service endpoint being routed to +// * @param info information about the request +// * +// * @return one of: an `HttpStatus` error code; a complete `Response`; or a new path to use in +// * place of the passed `uri` +// */ +// HttpStatus|ResponseOut|String handle(Dispatcher dispatcher, +// String uri, +// RequestInfo info, +// ) { +// if (uri.startsWith("session/")) { +// // e.g. "/xverify/session/123/456" comes in as "session/123/456" +// if (Int slash := uri.indexOf('/', 8), +// Int redirect := Int.parse(uri[8 ..< slash]), +// Int version := Int.parse(uri.substring(slash+1))) { +// return validateSessionCookies(dispatcher, uri, info, redirect, version); +// } +// } +// +// return NotFound; +// } +// +// /** +// * Implements the validation check for the session cookies when one or more session cookie has +// * been added. +// * +// * There are five expected outcomes: +// * +// * * Confirm - 99.9+% of the time, everything is perfect and exactly as expected +// * * Repeat - Occasionally, the redirect will need to be repeated because the session version +// * has already advanced for some reason before this redirect request was able to be processed +// * * Split - If anything is actually _wrong_, then the session is split, and a redirect is +// * returned for the user agent to validate against the split session +// * * New - If a session can't be found to validate against, or some other unrecoverable problem +// * appears to exist, then a new session is created, and a redirect is returned for the user +// * agent to validate the new session +// * * Abort - If something so fundamental is wrong that none of the above is an option, then the +// * method simply aborts with an error code +// * +// * @param uriString the URI that identifies the system service endpoint being routed to +// * @param info information about the request +// * @param redirect the redirection identifier previously registered with the session +// * @param version the session version being validated against +// * +// * @return one of: an `HttpStatus` error code; a complete `Response`; or a new path to use in +// * place of the passed `uriString` +// */ +// protected HttpStatus|ResponseOut validateSessionCookies(Dispatcher dispatcher, +// String uriString, +// RequestInfo info, +// Int64 redirect, +// Int64 version, +// ) { +// enum Action {Confirm, Repeat, Split, New} +// +// static Action evaluate(SessionImpl session, CookieId cookieId, String cookieText) { +// (Match_ match, SessionCookie? cookie) = session.cookieMatches_(cookieId, cookieText); +// switch (match) { +// case Correct: +// session.cookieVerified_(cookie ?: assert); +// return Confirm; +// +// case Older: +// // need to redirect (again) because the session version was just incremented +// // while we were waiting for the current redirect +// return Repeat; +// +// case Newer: +// // this is inconceivable, since we just redirected the user agent to confirm its +// // cookies, and we supposedly already handled the case that the user agent had +// // a "newer" cookie; treat this as a protocol violation +// return Split; +// +// case Corrupt: +// case WrongSession: +// // this is inconceivable, since we just found the session using this cookie, +// // but the cookie doesn't match; assume something so weird is going on that we +// // should just give up and return a new session to minimize any further damage +// case WrongCookieId: +// case Unexpected: +// // these are serious violations of the protocol; assume something so weird is +// // going on that we should just give up and return a new session to minimize +// // any further damage +// // TODO CP should report this to the session(s) +// default: +// return New; +// } +// } +// +// // find up to three session cookies that were passed in with the request +// (String? txtTemp, String? tlsTemp, String? consent, Int failures) +// = dispatcher.extractSessionCookies(info); +// if (failures != 0) { +// // something is deeply wrong with the cookies coming from the user agent, and it's +// // unlikely that we can fix them (because by the time that we got here, we supposedly +// // would have already tried to fix whatever that mess is) +// return BadRequest; +// } +// +// Boolean tls = info.tls; +// Action action = Confirm; +// +// // the plain-text cookie is required; use the plain text cookie to find the session; the +// // absence of either the cookie or the session implies that we should create a new session +// @Unassigned SessionImpl session; // note: assumed unassigned iff "action==New" +// Validate: if (txtTemp != Null, session := sessionManager.getSessionByCookie(txtTemp)) { +// // validate the plain text temporary cookie that we just used to look up the session +// action = evaluate(session, PlainText, txtTemp); +// +// // validate the temporary TLS cookie +// if (tlsTemp == Null) { +// // if the redirect came in on TLS, then the TLS "temporary" cookie should have been +// // included; treat it as a protocol violation and split the session +// if (tls && action != Repeat) { +// action = action.notLessThan(Split); +// } +// } else { +// action = action.notLessThan(evaluate(session, Encrypted, tlsTemp)); +// } +// +// // validate the persistent TLS cookie +// if (consent == Null) { +// // the consent cookie is required over TLS if the session has sent it out, unless +// // we've already determined that we need to redirect yet again) +// if (tls && session.usePersistentCookie_ && action != Repeat, +// (_, Time? sent) := session.getCookie_(Consent), sent != Null) { +// action = action.notLessThan(Split); +// } +// } else { +// action = action.notLessThan(evaluate(session, Consent, consent)); +// } +// } else { +// // unable to locate the specified session, so attempt to create a new session +// action = New; +// } +// +// switch (action) { +// case Confirm: +// // handle the most common case in which the redirect was successful +// ResponseOut response = new SimpleResponse(TemporaryRedirect); +// response.header[Header.Location] = session.claimRedirect_(redirect)?.toString() : "/"; +// return response; +// +// case Repeat: +// // handle the (rare) case in which we need to repeat the redirect, but only if the +// // actual session version is ahead of the version that this method was called to +// // validate (otherwise, repeating the validation isn't going to change anything) +// if (session.version_ > version) { +// break; +// } +// continue; +// case Split: +// // protocol error: split the session +// HttpStatus|SessionImpl result = session.split_(info); +// if (result.is(HttpStatus)) { +// return result; +// } +// session = result; +// break; +// +// case New: +// // create a new session +// HttpStatus|SessionImpl result = sessionManager.createSession(info); +// if (result.is(HttpStatus)) { +// return result; +// } +// session = result; +// break; +// } +// +// // send desired cookies (unless they've already been verified, in which case we never +// // re-send them) +// ResponseOut response = new SimpleResponse(TemporaryRedirect); +// Header header = response.header; +// Byte desired = session.desiredCookies_(tls); +// +// session.ensureCookies_(desired); +// +// for (CookieId cookieId : CookieId.from(desired)) { +// if ((SessionCookie resendCookie, Time? sent, Time? verified) +// := session.getCookie_(cookieId), verified == Null) { +// header.add(Header.SetCookie, resendCookie.toString()); +// if (sent == Null) { +// session.cookieSent_(resendCookie); +// } +// } +// } +// +// // erase undesired cookies +// Byte present = (txtTemp == Null ? 0 : CookieId.NoTls) +// | (tlsTemp == Null ? 0 : CookieId.TlsTemp) +// | (consent == Null ? 0 : CookieId.OnlyConsent); +// for (CookieId cookieId : CookieId.from(present & ~desired)) { +// header.add(Header.SetCookie, dispatcher.eraseCookie(cookieId)); +// } +// +// // come back to verify that the user agent received and subsequently sent the +// // cookies +// Uri newUri = new Uri(path=$"{catalog.services[0].path}/session/{redirect}/{session.version_}"); +// header[Header.Location] = newUri.toString(); +// return response; +// } +//} \ No newline at end of file diff --git a/lib_xenia/src/main/x/xenia/Dispatcher.x b/lib_xenia/src/main/x/xenia/Dispatcher.x index 3c1894b85..dd0be004f 100644 --- a/lib_xenia/src/main/x/xenia/Dispatcher.x +++ b/lib_xenia/src/main/x/xenia/Dispatcher.x @@ -28,13 +28,10 @@ import net.UriTemplate.UriParameters; service Dispatcher { construct(Catalog catalog, BundlePool bundlePool, - SessionManager sessionManager, - Authenticator authenticator, - SessionBroker sessionBroker) { - this.catalog = catalog; - this.bundlePool = bundlePool; - this.authenticator = authenticator; - this.sessionBroker = sessionBroker; + SessionManager sessionManager) { + this.catalog = catalog; + this.bundlePool = bundlePool; + this.sessionBroker = catalog.webApp.sessionBroker.duplicate(); } /** @@ -47,12 +44,6 @@ service Dispatcher { */ protected @Final BundlePool bundlePool; - /** - * The user authenticator, which encapsulates the process of challenging a client for - * authentication information and validating the resulting credentials. - */ - protected @Final Authenticator authenticator; - /** * The session broker, which encapsulates the process of establishing and identifying sessions. */ @@ -104,24 +95,6 @@ service Dispatcher { RequestIn request = new Http1Request(requestInfo, sessionBroker); response = catalog.webApp.handleUnhandledError^(request, HttpStatus.NotFound); } else { - Int wsid = serviceInfo.id; - if (wsid == 0) { - // this is a redirect or other system service call - bundle = bundlePool.allocateBundle(wsid); - SystemService svc = bundle.ensureWebService(wsid).as(SystemService); - HttpStatus|ResponseOut|String result = svc.handle(this, uriString, requestInfo); - if (result.is(String)) { - uriString = result; - bundlePool.releaseBundle(bundle); - continue FromTheBeginning; - } - - response = result.is(ResponseOut) - ? result - : new SimpleResponse(result); - break ProcessRequest; - } - // split what's left of the URI into a path, a query, and a fragment String? query = Null; String? fragment = Null; @@ -141,6 +114,7 @@ service Dispatcher { EndpointInfo endpoint; UriParameters uriParams = []; + Int wsid = serviceInfo.id; FindEndpoint: { Uri uri; try { @@ -224,7 +198,7 @@ service Dispatcher { if (sendNow == Null) { // this is the "normal" i.e. "actual" request processing bundle = bundlePool.allocateBundle(wsid); - Handler handle = bundle.ensureCallChain(endpoint, authenticator); + Handler handle = bundle.ensureCallChain(endpoint); response = handle^(request); } else { response = sendNow; diff --git a/lib_xenia/src/main/x/xenia/HttpHandler.x b/lib_xenia/src/main/x/xenia/HttpHandler.x index b6104b707..ed961a610 100644 --- a/lib_xenia/src/main/x/xenia/HttpHandler.x +++ b/lib_xenia/src/main/x/xenia/HttpHandler.x @@ -175,7 +175,7 @@ service HttpHandler Dispatcher[] dispatchers = this.dispatchers; Int count = dispatchers.size; if (count == 0) { - dispatchers.add(new Dispatcher(catalog, bundlePool, sessionManager, authenticator, new web.sessions.NeverBroker())); // TODO + dispatchers.add(new Dispatcher(catalog, bundlePool, sessionManager)); busy.add(True); lastIndex = 0; return 0; @@ -192,7 +192,7 @@ service HttpHandler } if (count < maxCount) { - dispatchers.add(new Dispatcher(catalog, bundlePool, sessionManager, authenticator, new web.sessions.NeverBroker())); // TODO + dispatchers.add(new Dispatcher(catalog, bundlePool, sessionManager)); busy.add(True); return count; // don't change the lastIndex to retain some fairness } diff --git a/lib_xenia/src/main/x/xenia/SystemService.x b/lib_xenia/src/main/x/xenia/SystemService.x deleted file mode 100644 index 1942fe101..000000000 --- a/lib_xenia/src/main/x/xenia/SystemService.x +++ /dev/null @@ -1,244 +0,0 @@ -import HttpServer.RequestInfo; -import SessionCookie.CookieId; -import SessionImpl.Match_; - -import web.Header; -import web.HttpStatus; - -import web.responses.SimpleResponse; - - -/** - * The SystemService provides end points for "system" functionality (like verifying HTTPS/TLS is - * enabled, handling some forms of authentication, etc.), and is automatically added to every - * Xenia-hosted web application. - */ -@WebService("/xverify") -service SystemService { - // ----- properties ---------------------------------------------------------------------------- - - /** - * The cached `Catalog`. - */ - @Lazy Catalog catalog.calc() { - assert val catalog := webApp.registry_.getResource("catalog"); - return catalog.as(Catalog); - } - - /** - * The cached `SessionManager`. - */ - @Lazy SessionManager sessionManager.calc() { - assert val mgr := webApp.registry_.getResource("sessionManager"); - return mgr.as(SessionManager); - } - - - // ----- HTTP endpoints ------------------------------------------------------------------------ - - /** - * Handle a system service call. This is the "endpoint" for the service. - * - * @param uri the URI string that identifies the system service endpoint being routed to - * @param info information about the request - * - * @return one of: an `HttpStatus` error code; a complete `Response`; or a new path to use in - * place of the passed `uri` - */ - HttpStatus|ResponseOut|String handle(Dispatcher dispatcher, - String uri, - RequestInfo info, - ) { - if (uri.startsWith("session/")) { - // e.g. "/xverify/session/123/456" comes in as "session/123/456" - if (Int slash := uri.indexOf('/', 8), - Int redirect := Int.parse(uri[8 ..< slash]), - Int version := Int.parse(uri.substring(slash+1))) { - return validateSessionCookies(dispatcher, uri, info, redirect, version); - } - } - - return NotFound; - } - - /** - * Implements the validation check for the session cookies when one or more session cookie has - * been added. - * - * There are five expected outcomes: - * - * * Confirm - 99.9+% of the time, everything is perfect and exactly as expected - * * Repeat - Occasionally, the redirect will need to be repeated because the session version - * has already advanced for some reason before this redirect request was able to be processed - * * Split - If anything is actually _wrong_, then the session is split, and a redirect is - * returned for the user agent to validate against the split session - * * New - If a session can't be found to validate against, or some other unrecoverable problem - * appears to exist, then a new session is created, and a redirect is returned for the user - * agent to validate the new session - * * Abort - If something so fundamental is wrong that none of the above is an option, then the - * method simply aborts with an error code - * - * @param uriString the URI that identifies the system service endpoint being routed to - * @param info information about the request - * @param redirect the redirection identifier previously registered with the session - * @param version the session version being validated against - * - * @return one of: an `HttpStatus` error code; a complete `Response`; or a new path to use in - * place of the passed `uriString` - */ - protected HttpStatus|ResponseOut validateSessionCookies(Dispatcher dispatcher, - String uriString, - RequestInfo info, - Int64 redirect, - Int64 version, - ) { - enum Action {Confirm, Repeat, Split, New} - - static Action evaluate(SessionImpl session, CookieId cookieId, String cookieText) { - (Match_ match, SessionCookie? cookie) = session.cookieMatches_(cookieId, cookieText); - switch (match) { - case Correct: - session.cookieVerified_(cookie ?: assert); - return Confirm; - - case Older: - // need to redirect (again) because the session version was just incremented - // while we were waiting for the current redirect - return Repeat; - - case Newer: - // this is inconceivable, since we just redirected the user agent to confirm its - // cookies, and we supposedly already handled the case that the user agent had - // a "newer" cookie; treat this as a protocol violation - return Split; - - case Corrupt: - case WrongSession: - // this is inconceivable, since we just found the session using this cookie, - // but the cookie doesn't match; assume something so weird is going on that we - // should just give up and return a new session to minimize any further damage - case WrongCookieId: - case Unexpected: - // these are serious violations of the protocol; assume something so weird is - // going on that we should just give up and return a new session to minimize - // any further damage - // TODO CP should report this to the session(s) - default: - return New; - } - } - - // find up to three session cookies that were passed in with the request - (String? txtTemp, String? tlsTemp, String? consent, Int failures) - = dispatcher.extractSessionCookies(info); - if (failures != 0) { - // something is deeply wrong with the cookies coming from the user agent, and it's - // unlikely that we can fix them (because by the time that we got here, we supposedly - // would have already tried to fix whatever that mess is) - return BadRequest; - } - - Boolean tls = info.tls; - Action action = Confirm; - - // the plain-text cookie is required; use the plain text cookie to find the session; the - // absence of either the cookie or the session implies that we should create a new session - @Unassigned SessionImpl session; // note: assumed unassigned iff "action==New" - Validate: if (txtTemp != Null, session := sessionManager.getSessionByCookie(txtTemp)) { - // validate the plain text temporary cookie that we just used to look up the session - action = evaluate(session, PlainText, txtTemp); - - // validate the temporary TLS cookie - if (tlsTemp == Null) { - // if the redirect came in on TLS, then the TLS "temporary" cookie should have been - // included; treat it as a protocol violation and split the session - if (tls && action != Repeat) { - action = action.notLessThan(Split); - } - } else { - action = action.notLessThan(evaluate(session, Encrypted, tlsTemp)); - } - - // validate the persistent TLS cookie - if (consent == Null) { - // the consent cookie is required over TLS if the session has sent it out, unless - // we've already determined that we need to redirect yet again) - if (tls && session.usePersistentCookie_ && action != Repeat, - (_, Time? sent) := session.getCookie_(Consent), sent != Null) { - action = action.notLessThan(Split); - } - } else { - action = action.notLessThan(evaluate(session, Consent, consent)); - } - } else { - // unable to locate the specified session, so attempt to create a new session - action = New; - } - - switch (action) { - case Confirm: - // handle the most common case in which the redirect was successful - ResponseOut response = new SimpleResponse(TemporaryRedirect); - response.header[Header.Location] = session.claimRedirect_(redirect)?.toString() : "/"; - return response; - - case Repeat: - // handle the (rare) case in which we need to repeat the redirect, but only if the - // actual session version is ahead of the version that this method was called to - // validate (otherwise, repeating the validation isn't going to change anything) - if (session.version_ > version) { - break; - } - continue; - case Split: - // protocol error: split the session - HttpStatus|SessionImpl result = session.split_(info); - if (result.is(HttpStatus)) { - return result; - } - session = result; - break; - - case New: - // create a new session - HttpStatus|SessionImpl result = sessionManager.createSession(info); - if (result.is(HttpStatus)) { - return result; - } - session = result; - break; - } - - // send desired cookies (unless they've already been verified, in which case we never - // re-send them) - ResponseOut response = new SimpleResponse(TemporaryRedirect); - Header header = response.header; - Byte desired = session.desiredCookies_(tls); - - session.ensureCookies_(desired); - - for (CookieId cookieId : CookieId.from(desired)) { - if ((SessionCookie resendCookie, Time? sent, Time? verified) - := session.getCookie_(cookieId), verified == Null) { - header.add(Header.SetCookie, resendCookie.toString()); - if (sent == Null) { - session.cookieSent_(resendCookie); - } - } - } - - // erase undesired cookies - Byte present = (txtTemp == Null ? 0 : CookieId.NoTls) - | (tlsTemp == Null ? 0 : CookieId.TlsTemp) - | (consent == Null ? 0 : CookieId.OnlyConsent); - for (CookieId cookieId : CookieId.from(present & ~desired)) { - header.add(Header.SetCookie, dispatcher.eraseCookie(cookieId)); - } - - // come back to verify that the user agent received and subsequently sent the - // cookies - Uri newUri = new Uri(path=$"{catalog.services[0].path}/session/{redirect}/{session.version_}"); - header[Header.Location] = newUri.toString(); - return response; - } -} \ No newline at end of file