From b82f82b7251abc56bbe249644e1ee2f29956f4e2 Mon Sep 17 00:00:00 2001 From: Muxi Yan Date: Mon, 14 Aug 2017 12:01:14 -0700 Subject: Revert "Revert "Add OAuth2 protocol in GRPCClient"" --- gRPC.podspec | 23 +++++-- src/objective-c/GRPCClient/GRPCCall+GID.h | 29 +++++++++ src/objective-c/GRPCClient/GRPCCall+GID.m | 28 +++++++++ src/objective-c/GRPCClient/GRPCCall+OAuth2.h | 15 +++++ src/objective-c/GRPCClient/GRPCCall+OAuth2.m | 11 ++++ src/objective-c/GRPCClient/GRPCCall.m | 73 +++++++++++++++------- .../GRPCClient/private/GRPCRequestHeaders.m | 1 - templates/gRPC.podspec.template | 23 +++++-- 8 files changed, 171 insertions(+), 32 deletions(-) create mode 100644 src/objective-c/GRPCClient/GRPCCall+GID.h create mode 100644 src/objective-c/GRPCClient/GRPCCall+GID.m diff --git a/gRPC.podspec b/gRPC.podspec index 4c83ccc7a0..22ecdcf66f 100644 --- a/gRPC.podspec +++ b/gRPC.podspec @@ -40,12 +40,9 @@ Pod::Spec.new do |s| s.header_dir = name src_dir = 'src/objective-c/GRPCClient' - s.source_files = "#{src_dir}/*.{h,m}", "#{src_dir}/**/*.{h,m}" - s.private_header_files = "#{src_dir}/private/*.h" - s.header_mappings_dir = "#{src_dir}" - s.dependency 'gRPC-Core', version s.dependency 'gRPC-RxLibrary', version + s.default_subspec = 'Main' # Certificates, to be able to establish TLS connections: s.resource_bundles = { 'gRPCCertificates' => ['etc/roots.pem'] } @@ -54,4 +51,22 @@ Pod::Spec.new do |s| # This is needed by all pods that depend on gRPC-RxLibrary: 'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES', } + + s.subspec 'Main' do |ss| + ss.header_mappings_dir = "#{src_dir}" + + ss.source_files = "#{src_dir}/*.{h,m}", "#{src_dir}/**/*.{h,m}" + ss.exclude_files = "#{src_dir}/GRPCCall+GID.{h,m}" + ss.private_header_files = "#{src_dir}/private/*.h" + + ss.dependency 'gRPC-Core', version + end + + s.subspec 'GID' do |ss| + ss.header_mappings_dir = "#{src_dir}" + + ss.source_files = "#{src_dir}/GRPCCall+GID.{h,m}" + + ss.dependency 'Google/SignIn' + end end diff --git a/src/objective-c/GRPCClient/GRPCCall+GID.h b/src/objective-c/GRPCClient/GRPCCall+GID.h new file mode 100644 index 0000000000..3ee732e79e --- /dev/null +++ b/src/objective-c/GRPCClient/GRPCCall+GID.h @@ -0,0 +1,29 @@ +/* + * + * Copyright 2017 gRPC authors. + * + * 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 "GRPCCall.h" +#import "GRPCCall+OAuth2.h" + +#import + +/** + * Extend GIDSignIn class to comply GRPCAuthorizationProtocol + */ +@interface GIDSignIn (GRPC) +- (void)getTokenWithHandler:(void (^)(NSString *token))hander; +@end diff --git a/src/objective-c/GRPCClient/GRPCCall+GID.m b/src/objective-c/GRPCClient/GRPCCall+GID.m new file mode 100644 index 0000000000..030737147b --- /dev/null +++ b/src/objective-c/GRPCClient/GRPCCall+GID.m @@ -0,0 +1,28 @@ +/* + * + * Copyright 2017 gRPC authors. + * + * 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 "GRPCCall+GID.h" + +@implementation GIDSignIn (GRPC) + +- (void)getTokenWithHandler:(void (^)(NSString *token))handler { + NSString *token = self.currentUser.authentication.accessToken; + handler(token); +} + +@end diff --git a/src/objective-c/GRPCClient/GRPCCall+OAuth2.h b/src/objective-c/GRPCClient/GRPCCall+OAuth2.h index 65465e9523..adb1042aa0 100644 --- a/src/objective-c/GRPCClient/GRPCCall+OAuth2.h +++ b/src/objective-c/GRPCClient/GRPCCall+OAuth2.h @@ -18,6 +18,13 @@ #import "GRPCCall.h" +/** + * The protocol of an OAuth2 token object from which GRPCCall can acquire a token. + */ +@protocol GRPCAuthorizationProtocol +- (void)getTokenWithHandler:(void (^)(NSString *token))hander; +@end + /** Helpers for setting and reading headers compatible with OAuth2. */ @interface GRPCCall (OAuth2) @@ -33,4 +40,12 @@ /** Returns the value (if any) of the "www-authenticate" response header (the challenge header). */ @property(atomic, readonly) NSString *oauth2ChallengeHeader; +/** + * The authorization token object to be used when starting the call. If the value is set to nil, no + * oauth authentication will be used. + * + * If tokenProvider exists, it takes precedence over the token set by oauth2AccessToken. + */ +@property(atomic, strong) id tokenProvider; + @end diff --git a/src/objective-c/GRPCClient/GRPCCall+OAuth2.m b/src/objective-c/GRPCClient/GRPCCall+OAuth2.m index eaa7465087..8451ebe870 100644 --- a/src/objective-c/GRPCClient/GRPCCall+OAuth2.m +++ b/src/objective-c/GRPCClient/GRPCCall+OAuth2.m @@ -16,6 +16,8 @@ * */ +#import + #import "GRPCCall+OAuth2.h" static NSString * const kAuthorizationHeader = @"authorization"; @@ -23,6 +25,7 @@ static NSString * const kBearerPrefix = @"Bearer "; static NSString * const kChallengeHeader = @"www-authenticate"; @implementation GRPCCall (OAuth2) +@dynamic tokenProvider; - (NSString *)oauth2AccessToken { NSString *headerValue = self.requestHeaders[kAuthorizationHeader]; @@ -45,4 +48,12 @@ static NSString * const kChallengeHeader = @"www-authenticate"; return self.responseHeaders[kChallengeHeader]; } +- (void)setTokenProvider:(id)tokenProvider { + objc_setAssociatedObject(self, @selector(tokenProvider), tokenProvider, OBJC_ASSOCIATION_RETAIN); +} + +- (id)tokenProvider { + return objc_getAssociatedObject(self, @selector(tokenProvider)); +} + @end diff --git a/src/objective-c/GRPCClient/GRPCCall.m b/src/objective-c/GRPCClient/GRPCCall.m index 872362419e..436c19e354 100644 --- a/src/objective-c/GRPCClient/GRPCCall.m +++ b/src/objective-c/GRPCClient/GRPCCall.m @@ -18,6 +18,8 @@ #import "GRPCCall.h" +#import "GRPCCall+OAuth2.h" + #include #include #import @@ -40,10 +42,14 @@ NSString * const kGRPCHeadersKey = @"io.grpc.HeadersKey"; NSString * const kGRPCTrailersKey = @"io.grpc.TrailersKey"; static NSMutableDictionary *callFlags; +static NSString * const kAuthorizationHeader = @"authorization"; +static NSString * const kBearerPrefix = @"Bearer "; + @interface GRPCCall () // Make them read-write. @property(atomic, strong) NSDictionary *responseHeaders; @property(atomic, strong) NSDictionary *responseTrailers; +@property(atomic) BOOL isWaitingForToken; @end // The following methods of a C gRPC call object aren't reentrant, and thus @@ -181,9 +187,6 @@ static NSMutableDictionary *callFlags; - (void)finishWithError:(NSError *)errorOrNil { @synchronized(self) { - if (_state == GRXWriterStateFinished) { - return; - } _state = GRXWriterStateFinished; } @@ -211,7 +214,11 @@ static NSMutableDictionary *callFlags; [self finishWithError:[NSError errorWithDomain:kGRPCErrorDomain code:GRPCErrorCodeCancelled userInfo:@{NSLocalizedDescriptionKey: @"Canceled by app"}]]; - [self cancelCall]; + if (!self.isWaitingForToken) { + [self cancelCall]; + } else { + self.isWaitingForToken = NO; + } } - (void)dealloc { @@ -410,22 +417,13 @@ static NSMutableDictionary *callFlags; #pragma mark GRXWriter implementation -- (void)startWithWriteable:(id)writeable { - @synchronized(self) { - _state = GRXWriterStateStarted; - } - - // Create a retain cycle so that this instance lives until the RPC finishes (or is cancelled). - // This makes RPCs in which the call isn't externally retained possible (as long as it is started - // before being autoreleased). - // Care is taken not to retain self strongly in any of the blocks used in this implementation, so - // that the life of the instance is determined by this retain cycle. - _retainSelf = self; - +- (void)startCallWithWriteable:(id)writeable { _responseWriteable = [[GRXConcurrentWriteable alloc] initWithWriteable:writeable dispatchQueue:_responseQueue]; - _wrappedCall = [[GRPCWrappedCall alloc] initWithHost:_host serverName:_serverName path:_path]; + _wrappedCall = [[GRPCWrappedCall alloc] initWithHost:_host + serverName:_serverName + path:_path]; NSAssert(_wrappedCall, @"Error allocating RPC objects. Low memory?"); [self sendHeaders:_requestHeaders]; @@ -437,20 +435,49 @@ static NSMutableDictionary *callFlags; // TODO(jcanizales): Check this on init. [NSException raise:NSInvalidArgumentException format:@"host of %@ is nil", _host]; } - __weak typeof(self) weakSelf = self; _connectivityMonitor = [GRPCConnectivityMonitor monitorWithHost:host]; + __weak typeof(self) weakSelf = self; void (^handler)() = ^{ typeof(self) strongSelf = weakSelf; - if (strongSelf) { - [strongSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain - code:GRPCErrorCodeUnavailable - userInfo:@{ NSLocalizedDescriptionKey : @"Connectivity lost." }]]; - } + [strongSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain + code:GRPCErrorCodeUnavailable + userInfo:@{ NSLocalizedDescriptionKey : @"Connectivity lost." }]]; }; [_connectivityMonitor handleLossWithHandler:handler wifiStatusChangeHandler:nil]; } +- (void)startWithWriteable:(id)writeable { + @synchronized(self) { + _state = GRXWriterStateStarted; + } + + // Create a retain cycle so that this instance lives until the RPC finishes (or is cancelled). + // This makes RPCs in which the call isn't externally retained possible (as long as it is started + // before being autoreleased). + // Care is taken not to retain self strongly in any of the blocks used in this implementation, so + // that the life of the instance is determined by this retain cycle. + _retainSelf = self; + + if (self.tokenProvider != nil) { + self.isWaitingForToken = YES; + __weak typeof(self) weakSelf = self; + [self.tokenProvider getTokenWithHandler:^(NSString *token){ + typeof(self) strongSelf = weakSelf; + if (strongSelf && strongSelf.isWaitingForToken) { + if (token) { + NSString *t = [kBearerPrefix stringByAppendingString:token]; + strongSelf.requestHeaders[kAuthorizationHeader] = t; + } + [strongSelf startCallWithWriteable:writeable]; + strongSelf.isWaitingForToken = NO; + } + }]; + } else { + [self startCallWithWriteable:writeable]; + } +} + - (void)setState:(GRXWriterState)newState { @synchronized(self) { // Manual transitions are only allowed from the started or paused states. diff --git a/src/objective-c/GRPCClient/private/GRPCRequestHeaders.m b/src/objective-c/GRPCClient/private/GRPCRequestHeaders.m index 7640a64d6d..5de1d8fff5 100644 --- a/src/objective-c/GRPCClient/private/GRPCRequestHeaders.m +++ b/src/objective-c/GRPCClient/private/GRPCRequestHeaders.m @@ -103,7 +103,6 @@ static void CheckKeyValuePairIsValid(NSString *key, id value) { } - (void)setObject:(id)obj forKey:(NSString *)key { - [self checkCallIsNotStarted]; CheckIsNonNilASCII(@"Header name", key); key = key.lowercaseString; CheckKeyValuePairIsValid(key, obj); diff --git a/templates/gRPC.podspec.template b/templates/gRPC.podspec.template index 62a6d37c3c..5c92f9f9c4 100644 --- a/templates/gRPC.podspec.template +++ b/templates/gRPC.podspec.template @@ -42,12 +42,9 @@ s.header_dir = name src_dir = 'src/objective-c/GRPCClient' - s.source_files = "#{src_dir}/*.{h,m}", "#{src_dir}/**/*.{h,m}" - s.private_header_files = "#{src_dir}/private/*.h" - s.header_mappings_dir = "#{src_dir}" - s.dependency 'gRPC-Core', version s.dependency 'gRPC-RxLibrary', version + s.default_subspec = 'Main' # Certificates, to be able to establish TLS connections: s.resource_bundles = { 'gRPCCertificates' => ['etc/roots.pem'] } @@ -56,4 +53,22 @@ # This is needed by all pods that depend on gRPC-RxLibrary: 'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES', } + + s.subspec 'Main' do |ss| + ss.header_mappings_dir = "#{src_dir}" + + ss.source_files = "#{src_dir}/*.{h,m}", "#{src_dir}/**/*.{h,m}" + ss.exclude_files = "#{src_dir}/GRPCCall+GID.{h,m}" + ss.private_header_files = "#{src_dir}/private/*.h" + + ss.dependency 'gRPC-Core', version + end + + s.subspec 'GID' do |ss| + ss.header_mappings_dir = "#{src_dir}" + + ss.source_files = "#{src_dir}/GRPCCall+GID.{h,m}" + + ss.dependency 'Google/SignIn' + end end -- cgit v1.2.3