-
Notifications
You must be signed in to change notification settings - Fork 780
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement OIDC RP-Initiated Logout #259
Changes from all commits
68648b9
49380af
9ea9107
d62069a
1b8fd79
d5e8f6a
ea5ad45
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,8 @@ | |
#import "OIDAuthorizationRequest.h" | ||
#import "OIDAuthorizationResponse.h" | ||
#import "OIDDefines.h" | ||
#import "OIDEndSessionRequest.h" | ||
#import "OIDEndSessionResponse.h" | ||
#import "OIDErrorUtilities.h" | ||
#import "OIDExternalUserAgent.h" | ||
#import "OIDExternalUserAgentSession.h" | ||
|
@@ -92,9 +94,14 @@ - (void)cancel { | |
}]; | ||
} | ||
|
||
- (BOOL)shouldHandleURL:(NSURL *)URL { | ||
/*! @brief Does the redirection URL equal another URL down to the path component? | ||
@param URL The first redirect URI to compare. | ||
@param redirectonURL The second redirect URI to compare. | ||
@return YES if the URLs match down to the path level (query params are ignored). | ||
*/ | ||
+ (BOOL)URL:(NSURL *)URL matchesRedirectonURL:(NSURL *)redirectonURL { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo |
||
NSURL *standardizedURL = [URL standardizedURL]; | ||
NSURL *standardizedRedirectURL = [_request.redirectURL standardizedURL]; | ||
NSURL *standardizedRedirectURL = [redirectonURL standardizedURL]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
return [standardizedURL.scheme caseInsensitiveCompare:standardizedRedirectURL.scheme] == NSOrderedSame | ||
&& OIDIsEqualIncludingNil(standardizedURL.user, standardizedRedirectURL.user) | ||
|
@@ -104,6 +111,10 @@ - (BOOL)shouldHandleURL:(NSURL *)URL { | |
&& OIDIsEqualIncludingNil(standardizedURL.path, standardizedRedirectURL.path); | ||
} | ||
|
||
- (BOOL)shouldHandleURL:(NSURL *)URL { | ||
return [[self class] URL:URL matchesRedirectonURL:_request.redirectURL]; | ||
} | ||
|
||
- (BOOL)resumeExternalUserAgentFlowWithURL:(NSURL *)URL { | ||
// rejects URLs that don't match redirect (these may be completely unrelated to the authorization) | ||
if (![self shouldHandleURL:URL]) { | ||
|
@@ -178,6 +189,121 @@ - (void)didFinishWithResponse:(nullable OIDAuthorizationResponse *)response | |
|
||
@end | ||
|
||
@interface OIDEndSessionImplementation : NSObject<OIDExternalUserAgentSession> { | ||
// private variables | ||
OIDEndSessionRequest *_request; | ||
id<OIDExternalUserAgent> _externalUserAgent; | ||
OIDEndSessionCallback _pendingEndSessionCallback; | ||
} | ||
- (instancetype)init NS_UNAVAILABLE; | ||
|
||
- (instancetype)initWithRequest:(OIDEndSessionRequest *)request | ||
NS_DESIGNATED_INITIALIZER; | ||
@end | ||
|
||
|
||
@implementation OIDEndSessionImplementation | ||
|
||
- (instancetype)initWithRequest:(OIDEndSessionRequest *)request { | ||
self = [super init]; | ||
if (self) { | ||
_request = [request copy]; | ||
} | ||
return self; | ||
} | ||
|
||
- (void)presentAuthorizationWithExternalUserAgent:(id<OIDExternalUserAgent>)externalUserAgent | ||
callback:(OIDEndSessionCallback)authorizationFlowCallback { | ||
_externalUserAgent = externalUserAgent; | ||
_pendingEndSessionCallback = authorizationFlowCallback; | ||
BOOL authorizationFlowStarted = | ||
[_externalUserAgent presentExternalUserAgentRequest:_request session:self]; | ||
if (!authorizationFlowStarted) { | ||
NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeSafariOpenError | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know this is not directly related to this PR, but I wonder if this error type should be renamed to something like |
||
underlyingError:nil | ||
description:@"Unable to open Safari."]; | ||
[self didFinishWithResponse:nil error:safariError]; | ||
} | ||
} | ||
|
||
- (void)cancel { | ||
[_externalUserAgent dismissExternalUserAgentAnimated:YES completion:^{ | ||
NSError *error = [OIDErrorUtilities | ||
errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow | ||
underlyingError:nil | ||
description:nil]; | ||
[self didFinishWithResponse:nil error:error]; | ||
}]; | ||
} | ||
|
||
- (BOOL)shouldHandleURL:(NSURL *)URL { | ||
// The logic of when to handle the URL is the same as for authorization requests: should match | ||
// down to the path component. | ||
return [[OIDAuthorizationSession class] URL:URL | ||
matchesRedirectonURL:_request.postLogoutRedirectURL]; | ||
} | ||
|
||
- (BOOL)resumeExternalUserAgentFlowWithURL:(NSURL *)URL { | ||
// rejects URLs that don't match redirect (these may be completely unrelated to the authorization) | ||
if (![self shouldHandleURL:URL]) { | ||
return NO; | ||
} | ||
// checks for an invalid state | ||
if (!_pendingEndSessionCallback) { | ||
[NSException raise:OIDOAuthExceptionInvalidAuthorizationFlow | ||
format:@"%@", OIDOAuthExceptionInvalidAuthorizationFlow, nil]; | ||
} | ||
|
||
|
||
NSError *error; | ||
OIDEndSessionResponse *response = nil; | ||
|
||
OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] initWithURL:URL]; | ||
response = [[OIDEndSessionResponse alloc] initWithRequest:_request | ||
parameters:query.dictionaryValue]; | ||
|
||
// verifies that the state in the response matches the state in the request, or both are nil | ||
if (!OIDIsEqualIncludingNil(_request.state, response.state)) { | ||
NSMutableDictionary *userInfo = [query.dictionaryValue mutableCopy]; | ||
userInfo[NSLocalizedDescriptionKey] = | ||
[NSString stringWithFormat:@"State mismatch, expecting %@ but got %@ in authorization " | ||
"response %@", | ||
_request.state, | ||
response.state, | ||
response]; | ||
response = nil; | ||
error = [NSError errorWithDomain:OIDOAuthAuthorizationErrorDomain | ||
code:OIDErrorCodeOAuthAuthorizationClientError | ||
userInfo:userInfo]; | ||
} | ||
|
||
[_externalUserAgent dismissExternalUserAgentAnimated:YES completion:^{ | ||
[self didFinishWithResponse:response error:error]; | ||
}]; | ||
|
||
return YES; | ||
} | ||
|
||
- (void)failExternalUserAgentFlowWithError:(NSError *)error { | ||
[self didFinishWithResponse:nil error:error]; | ||
} | ||
|
||
/*! @brief Invokes the pending callback and performs cleanup. | ||
@param response The authorization response, if any to return to the callback. | ||
@param error The error, if any, to return to the callback. | ||
*/ | ||
- (void)didFinishWithResponse:(nullable OIDEndSessionResponse *)response | ||
error:(nullable NSError *)error { | ||
OIDEndSessionCallback callback = _pendingEndSessionCallback; | ||
_pendingEndSessionCallback = nil; | ||
_externalUserAgent = nil; | ||
if (callback) { | ||
callback(response, error); | ||
} | ||
} | ||
|
||
@end | ||
|
||
@implementation OIDAuthorizationService | ||
|
||
+ (void)discoverServiceConfigurationForIssuer:(NSURL *)issuerURL | ||
|
@@ -272,6 +398,16 @@ + (void)discoverServiceConfigurationForDiscoveryURL:(NSURL *)discoveryURL | |
return flowSession; | ||
} | ||
|
||
+ (id<OIDExternalUserAgentSession>) | ||
presentEndSessionRequest:(OIDEndSessionRequest *)request | ||
externalUserAgent:(id<OIDExternalUserAgent>)externalUserAgent | ||
callback:(OIDEndSessionCallback)callback { | ||
OIDEndSessionImplementation *flowSession = | ||
[[OIDEndSessionImplementation alloc] initWithRequest:request]; | ||
[flowSession presentAuthorizationWithExternalUserAgent:externalUserAgent callback:callback]; | ||
return flowSession; | ||
} | ||
|
||
#pragma mark - Token Endpoint | ||
|
||
+ (void)performTokenRequest:(OIDTokenRequest *)request callback:(OIDTokenCallback)callback { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
/*! @file OIDEndSessionRequest.h | ||
@brief AppAuth iOS SDK | ||
@copyright | ||
Copyright 2017 The AppAuth Authors. All Rights Reserved. | ||
@copydetails | ||
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. | ||
*/ | ||
|
||
#import <Foundation/Foundation.h> | ||
|
||
#import "OIDExternalUserAgentRequest.h" | ||
|
||
@class OIDServiceConfiguration; | ||
|
||
NS_ASSUME_NONNULL_BEGIN | ||
|
||
@interface OIDEndSessionRequest : NSObject | ||
<NSCopying, NSSecureCoding, OIDExternalUserAgentRequest> | ||
|
||
/*! @brief The service's configuration. | ||
@remarks This configuration specifies how to connect to a particular OAuth provider. | ||
Configurations may be created manually, or via an OpenID Connect Discovery Document. | ||
*/ | ||
@property(nonatomic, readonly) OIDServiceConfiguration *configuration; | ||
|
||
/*! @brief The client's redirect URI. | ||
@remarks post_logout_redirect_uri | ||
@see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout | ||
*/ | ||
@property(nonatomic, readonly, nullable) NSURL *postLogoutRedirectURL; | ||
|
||
/*! @brief Previously issued ID Token passed to the end session endpoint as a hint about the End-User's current authenticated | ||
session with the Client | ||
@remarks id_token_hint | ||
@see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout | ||
*/ | ||
@property(nonatomic, readonly, nullable) NSString *idTokenHint; | ||
|
||
/*! @brief An opaque value used by the client to maintain state between the request and callback. | ||
@remarks state | ||
@discussion If this value is not explicitly set, this library will automatically add state and | ||
perform appropriate validation of the state in the authorization response. It is recommended | ||
that the default implementation of this parameter be used wherever possible. Typically used | ||
to prevent CSRF attacks, as recommended in RFC6819 Section 5.3.5. | ||
@see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout | ||
*/ | ||
@property(nonatomic, readonly, nullable) NSString *state; | ||
|
||
/*! @brief The client's additional authorization parameters. | ||
@see https://tools.ietf.org/html/rfc6749#section-3.1 | ||
*/ | ||
@property(nonatomic, readonly, nullable) NSDictionary<NSString *, NSString *> *additionalParameters; | ||
|
||
/*! @internal | ||
@brief Unavailable. Please use @c initWithConfiguration:clientId:scopes:redirectURL:additionalParameters:. | ||
*/ | ||
- (instancetype)init NS_UNAVAILABLE; | ||
|
||
/*! @brief Creates an authorization request with opinionated defaults (a secure @c state). | ||
@param configuration The service's configuration. | ||
@param idTokenHint The previously issued ID Token | ||
@param postLogoutRedirectURL The client's post-logout redirect URI. | ||
callback. | ||
@param additionalParameters The client's additional authorization parameters. | ||
*/ | ||
- (instancetype) | ||
initWithConfiguration:(OIDServiceConfiguration *)configuration | ||
idTokenHint:(NSString *)idTokenHint | ||
postLogoutRedirectURL:(NSURL *)postLogoutRedirectURL | ||
additionalParameters:(nullable NSDictionary<NSString *, NSString *> *)additionalParameters; | ||
|
||
/*! @brief Designated initializer. | ||
@param configuration The service's configuration. | ||
@param idTokenHint The previously issued ID Token | ||
@param postLogoutRedirectURL The client's post-logout redirect URI. | ||
@param state An opaque value used by the client to maintain state between the request and | ||
callback. | ||
@param additionalParameters The client's additional authorization parameters. | ||
*/ | ||
- (instancetype) | ||
initWithConfiguration:(OIDServiceConfiguration *)configuration | ||
idTokenHint:(NSString *)idTokenHint | ||
postLogoutRedirectURL:(NSURL *)postLogoutRedirectURL | ||
state:(NSString *)state | ||
additionalParameters:(nullable NSDictionary<NSString *, NSString *> *)additionalParameters | ||
NS_DESIGNATED_INITIALIZER; | ||
|
||
/*! @brief Constructs the request URI by adding the request parameters to the query component of the | ||
authorization endpoint URI using the "application/x-www-form-urlencoded" format. | ||
@return A URL representing the authorization request. | ||
@see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout | ||
*/ | ||
- (NSURL *)endSessionRequestURL; | ||
|
||
@end | ||
|
||
NS_ASSUME_NONNULL_END |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo
redirectionURL