Explorar o código

Fix passing nil for nonce and add example (#476)

mdmathias hai 1 ano
pai
achega
72b9e63074

+ 21 - 12
GoogleSignIn/Sources/GIDSignIn.m

@@ -726,14 +726,23 @@ static NSString *const kConfigOpenIDRealmKey = @"GIDOpenIDRealm";
 - (OIDAuthorizationRequest *)
     authorizationRequestWithOptions:(GIDSignInInternalOptions *)options
                additionalParameters:(NSDictionary<NSString *, NSString *> *)additionalParameters {
-  OIDAuthorizationRequest *request =
-      [[OIDAuthorizationRequest alloc] initWithConfiguration:_appAuthConfiguration
-                                                    clientId:options.configuration.clientID
-                                                      scopes:options.scopes
-                                                 redirectURL:[self redirectURLWithOptions:options]
-                                                responseType:OIDResponseTypeCode
-                                                       nonce:options.nonce
-                                        additionalParameters:additionalParameters];
+  OIDAuthorizationRequest *request;
+  if (options.nonce) {
+    request = [[OIDAuthorizationRequest alloc] initWithConfiguration:_appAuthConfiguration
+                                                            clientId:options.configuration.clientID
+                                                              scopes:options.scopes
+                                                         redirectURL:[self redirectURLWithOptions:options]
+                                                        responseType:OIDResponseTypeCode
+                                                               nonce:options.nonce
+                                                additionalParameters:additionalParameters];
+  } else {
+    request = [[OIDAuthorizationRequest alloc] initWithConfiguration:_appAuthConfiguration
+                                                            clientId:options.configuration.clientID
+                                                              scopes:options.scopes
+                                                         redirectURL:[self redirectURLWithOptions:options]
+                                                        responseType:OIDResponseTypeCode
+                                                additionalParameters:additionalParameters];
+  }
   return request;
 }
 
@@ -918,10 +927,10 @@ static NSString *const kConfigOpenIDRealmKey = @"GIDOpenIDRealm";
   }
 
   [authFlow wait];
-  [OIDAuthorizationService
-      performTokenRequest:tokenRequest
-                 callback:^(OIDTokenResponse *_Nullable tokenResponse,
-                            NSError *_Nullable error) {
+  [OIDAuthorizationService performTokenRequest:tokenRequest
+                 originalAuthorizationResponse:authFlow.authState.lastAuthorizationResponse
+                                      callback:^(OIDTokenResponse *_Nullable tokenResponse,
+                                                 NSError *_Nullable error) {
     [authState updateWithTokenResponse:tokenResponse error:error];
     authFlow.error = error;
 

+ 3 - 2
GoogleSignIn/Tests/Unit/GIDSignInTest.m

@@ -297,7 +297,7 @@ static NSString *const kNewScope = @"newScope";
 #elif TARGET_OS_OSX
   _presentingWindow = OCMStrictClassMock([NSWindow class]);
 #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
-  _authState = OCMStrictClassMock([OIDAuthState class]);
+  _authState = OCMClassMock([OIDAuthState class]);
   OCMStub([_authState alloc]).andReturn(_authState);
   OCMStub([_authState initWithAuthorizationResponse:OCMOCK_ANY]).andReturn(_authState);
   _tokenResponse = OCMStrictClassMock([OIDTokenResponse class]);
@@ -327,7 +327,8 @@ static NSString *const kNewScope = @"newScope";
                          callback:COPY_TO_ARG_BLOCK(self->_savedAuthorizationCallback)]);
   OCMStub([self->_oidAuthorizationService
       performTokenRequest:SAVE_TO_ARG_BLOCK(self->_savedTokenRequest)
-                 callback:COPY_TO_ARG_BLOCK(self->_savedTokenCallback)]);
+      originalAuthorizationResponse:[OCMArg any]
+      callback:COPY_TO_ARG_BLOCK(self->_savedTokenCallback)]);
 
   // Fakes
   _fetcherService = [[GIDFakeFetcherService alloc] init];

+ 55 - 2
Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift

@@ -35,12 +35,28 @@ final class GoogleSignInAuthenticator: ObservableObject {
       print("There is no root view controller!")
       return
     }
-
-    GIDSignIn.sharedInstance.signIn(withPresenting: rootViewController) { signInResult, error in
+    let manualNonce = UUID().uuidString
+
+    GIDSignIn.sharedInstance.signIn(
+      withPresenting: rootViewController,
+      hint: nil,
+      additionalScopes: nil,
+      nonce: manualNonce
+    ) { signInResult, error in
       guard let signInResult = signInResult else {
         print("Error! \(String(describing: error))")
         return
       }
+
+      // Per OpenID Connect Core section 3.1.3.7, rule #11, compare returned nonce to manual
+      guard let idToken = signInResult.user.idToken?.tokenString,
+            let returnedNonce = self.decodeNonce(fromJWT: idToken),
+            returnedNonce == manualNonce else {
+        // Assert a failure for convenience so that integration tests with this sample app fail upon
+        // `nonce` mismatch
+        assertionFailure("ERROR: Returned nonce doesn't match manual nonce!")
+        return
+      }
       self.authViewModel.state = .signedIn(signInResult.user)
     }
 
@@ -125,3 +141,40 @@ final class GoogleSignInAuthenticator: ObservableObject {
   }
 
 }
+
+// MARK: Parse nonce from JWT ID Token
+
+private extension GoogleSignInAuthenticator {
+  func decodeNonce(fromJWT jwt: String) -> String? {
+    let segments = jwt.components(separatedBy: ".")
+    guard let parts = decodeJWTSegment(segments[1]),
+          let nonce = parts["nonce"] as? String else {
+      return nil
+    }
+    return nonce
+  }
+
+  func decodeJWTSegment(_ segment: String) -> [String: Any]? {
+    guard let segmentData = base64UrlDecode(segment),
+          let segmentJSON = try? JSONSerialization.jsonObject(with: segmentData, options: []),
+          let payload = segmentJSON as? [String: Any] else {
+      return nil
+    }
+    return payload
+  }
+
+  func base64UrlDecode(_ value: String) -> Data? {
+    var base64 = value
+      .replacingOccurrences(of: "-", with: "+")
+      .replacingOccurrences(of: "_", with: "/")
+
+    let length = Double(base64.lengthOfBytes(using: String.Encoding.utf8))
+    let requiredLength = 4 * ceil(length / 4.0)
+    let paddingLength = requiredLength - length
+    if paddingLength > 0 {
+      let padding = "".padding(toLength: Int(paddingLength), withPad: "=", startingAt: 0)
+      base64 = base64 + padding
+    }
+    return Data(base64Encoded: base64, options: .ignoreUnknownCharacters)
+  }
+}