Skip to content

Commit 51868b3

Browse files
authored
GIDTokenClaimsInternalOptions Implementation + Unit Tests #550 (#552)
This pull request introduces the `GIDTokenClaimsInternalOptions` class, a new component designed to handle the validation and JSON serialization of token claims. Key changes: * Adds the `GIDTokenClaimsInternalOptions` class to validate the token claims and return a JSON object. * Adds the `GIDJSONSerializer` protocol with real and fake implementations to support serializing the token claims. * Provides unit tests to validate the implementation.
1 parent 7f75975 commit 51868b3

File tree

9 files changed

+456
-0
lines changed

9 files changed

+456
-0
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import <Foundation/Foundation.h>
18+
19+
NS_ASSUME_NONNULL_BEGIN
20+
21+
/**
22+
* A protocol for serializing an `NSDictionary` into a JSON string.
23+
*/
24+
@protocol GIDJSONSerializer <NSObject>
25+
26+
/**
27+
* Serializes the given dictionary into a `JSON` string.
28+
*
29+
* @param jsonObject The dictionary to be serialized.
30+
* @param error A pointer to an `NSError` object to be populated upon failure.
31+
* @return A `JSON` string representation of the dictionary, or `nil` if an error occurs.
32+
*/
33+
- (nullable NSString *)stringWithJSONObject:(NSDictionary<NSString *, id> *)jsonObject
34+
error:(NSError *_Nullable *_Nullable)error;
35+
36+
@end
37+
38+
NS_ASSUME_NONNULL_END
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import "GoogleSignIn/Sources/GIDJSONSerializer/API/GIDJSONSerializer.h"
18+
19+
NS_ASSUME_NONNULL_BEGIN
20+
21+
/** A fake implementation of `GIDJSONSerializer` for testing purposes. */
22+
@interface GIDFakeJSONSerializerImpl : NSObject <GIDJSONSerializer>
23+
24+
/**
25+
* An error to be returned by `stringWithJSONObject:error:`.
26+
*
27+
* If this property is set, `stringWithJSONObject:error:` will return `nil` and
28+
* populate the error parameter with this error.
29+
*/
30+
@property(nonatomic, nullable) NSError *serializationError;
31+
32+
/** The dictionary passed to the serialization method. */
33+
@property(nonatomic, readonly, nullable) NSDictionary<NSString *, id> *capturedJSONObject;
34+
35+
@end
36+
37+
NS_ASSUME_NONNULL_END
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import "GoogleSignIn/Sources/GIDJSONSerializer/Fake/GIDFakeJSONSerializerImpl.h"
18+
19+
#import "GoogleSignIn/Sources/GIDJSONSerializer/Implementation/GIDJSONSerializerImpl.h"
20+
#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h"
21+
22+
@implementation GIDFakeJSONSerializerImpl
23+
24+
- (nullable NSString *)stringWithJSONObject:(NSDictionary<NSString *, id> *)jsonObject
25+
error:(NSError *_Nullable *_Nullable)error {
26+
_capturedJSONObject = [jsonObject copy];
27+
28+
// Check if a serialization error should be simulated.
29+
if (self.serializationError) {
30+
if (error) {
31+
*error = self.serializationError;
32+
}
33+
return nil;
34+
}
35+
36+
// If not failing, fall back to the real serialization path.
37+
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonObject
38+
options:0
39+
error:error];
40+
if (!jsonData) {
41+
return nil;
42+
}
43+
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
44+
}
45+
46+
@end
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import "GoogleSignIn/Sources/GIDJSONSerializer/API/GIDJSONSerializer.h"
18+
19+
NS_ASSUME_NONNULL_BEGIN
20+
21+
extern NSString *const kGIDJSONSerializationErrorDescription;
22+
23+
@interface GIDJSONSerializerImpl : NSObject <GIDJSONSerializer>
24+
@end
25+
26+
NS_ASSUME_NONNULL_END
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import "GoogleSignIn/Sources/GIDJSONSerializer/Implementation/GIDJSONSerializerImpl.h"
18+
19+
#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h"
20+
21+
NSString * const kGIDJSONSerializationErrorDescription =
22+
@"The provided object could not be serialized to a JSON string.";
23+
24+
@implementation GIDJSONSerializerImpl
25+
26+
- (nullable NSString *)stringWithJSONObject:(NSDictionary<NSString *, id> *)jsonObject
27+
error:(NSError *_Nullable *_Nullable)error {
28+
NSError *serializationError;
29+
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonObject
30+
options:0
31+
error:&serializationError];
32+
if (!jsonData) {
33+
if (error) {
34+
*error = [NSError errorWithDomain:kGIDSignInErrorDomain
35+
code:kGIDSignInErrorCodeJSONSerializationFailure
36+
userInfo:@{
37+
NSLocalizedDescriptionKey:kGIDJSONSerializationErrorDescription,
38+
NSUnderlyingErrorKey:serializationError
39+
}];
40+
}
41+
return nil;
42+
}
43+
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
44+
}
45+
46+
@end
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import <Foundation/Foundation.h>
18+
19+
@class GIDTokenClaim;
20+
21+
NS_ASSUME_NONNULL_BEGIN
22+
23+
extern NSString *const kGIDTokenClaimErrorDescription;
24+
extern NSString *const kGIDTokenClaimEssentialPropertyKeyName;
25+
extern NSString *const kGIDTokenClaimKeyName;
26+
27+
@protocol GIDJSONSerializer;
28+
29+
/**
30+
* An internal utility class for processing and serializing the `NSSet` of `GIDTokenClaim` objects
31+
* into the `JSON` format required for an `OIDAuthorizationRequest`.
32+
*/
33+
@interface GIDTokenClaimsInternalOptions : NSObject
34+
35+
- (instancetype)init;
36+
37+
- (instancetype)initWithJSONSerializer:
38+
(id<GIDJSONSerializer>)jsonSerializer NS_DESIGNATED_INITIALIZER;
39+
40+
/**
41+
* Processes the `NSSet` of `GIDTokenClaim` objects, handling ambiguous claims,
42+
* and returns a `JSON` string.
43+
*
44+
* @param claims The `NSSet` of `GIDTokenClaim` objects provided by the developer.
45+
* @param error A pointer to an `NSError` object to be populated if an error occurs (e.g., if a
46+
* claim is requested as both essential and non-essential).
47+
* @return A `JSON` string representing the claims request, or `nil` if the input is empty or an
48+
* error occurs.
49+
*/
50+
- (nullable NSString *)validatedJSONStringForClaims:(nullable NSSet<GIDTokenClaim *> *)claims
51+
error:(NSError *_Nullable *_Nullable)error;
52+
53+
@end
54+
55+
NS_ASSUME_NONNULL_END
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import "GoogleSignIn/Sources/GIDTokenClaimsInternalOptions.h"
18+
19+
#import "GoogleSignIn/Sources/GIDJSONSerializer/API/GIDJSONSerializer.h"
20+
#import "GoogleSignIn/Sources/GIDJSONSerializer/Implementation/GIDJSONSerializerImpl.h"
21+
#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h"
22+
#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDTokenClaim.h"
23+
24+
NSString * const kGIDTokenClaimErrorDescription =
25+
@"The claim was requested as both essential and non-essential. "
26+
@"Please provide only one version.";
27+
NSString * const kGIDTokenClaimEssentialPropertyKey = @"essential";
28+
NSString * const kGIDTokenClaimKeyName = @"id_token";
29+
30+
@interface GIDTokenClaimsInternalOptions ()
31+
@property(nonatomic, readonly) id<GIDJSONSerializer> jsonSerializer;
32+
@end
33+
34+
@implementation GIDTokenClaimsInternalOptions
35+
36+
- (instancetype)init {
37+
return [self initWithJSONSerializer:[[GIDJSONSerializerImpl alloc] init]];
38+
}
39+
40+
- (instancetype)initWithJSONSerializer:(id<GIDJSONSerializer>)jsonSerializer {
41+
if (self = [super init]) {
42+
_jsonSerializer = jsonSerializer;
43+
}
44+
return self;
45+
}
46+
47+
- (nullable NSString *)validatedJSONStringForClaims:(nullable NSSet<GIDTokenClaim *> *)claims
48+
error:(NSError *_Nullable *_Nullable)error {
49+
if (!claims || claims.count == 0) {
50+
return nil;
51+
}
52+
53+
// === Step 1: Check for claims with ambiguous essential property. ===
54+
NSMutableDictionary<NSString *, GIDTokenClaim *> *validTokenClaims =
55+
[[NSMutableDictionary alloc] init];
56+
57+
for (GIDTokenClaim *currentClaim in claims) {
58+
GIDTokenClaim *existingClaim = validTokenClaims[currentClaim.name];
59+
60+
// Check for a conflict: a claim with the same name but different essentiality.
61+
if (existingClaim && existingClaim.isEssential != currentClaim.isEssential) {
62+
if (error) {
63+
*error = [NSError errorWithDomain:kGIDSignInErrorDomain
64+
code:kGIDSignInErrorCodeAmbiguousClaims
65+
userInfo:@{
66+
NSLocalizedDescriptionKey:kGIDTokenClaimErrorDescription
67+
}];
68+
}
69+
return nil;
70+
}
71+
validTokenClaims[currentClaim.name] = currentClaim;
72+
}
73+
74+
// === Step 2: Build the dictionary structure required for OIDC JSON ===
75+
NSMutableDictionary<NSString *, NSDictionary *> *tokenClaimsDictionary =
76+
[[NSMutableDictionary alloc] init];
77+
for (GIDTokenClaim *claim in validTokenClaims.allValues) {
78+
if (claim.isEssential) {
79+
tokenClaimsDictionary[claim.name] = @{ kGIDTokenClaimEssentialPropertyKey: @YES };
80+
} else {
81+
tokenClaimsDictionary[claim.name] = @{ kGIDTokenClaimEssentialPropertyKey: @NO };
82+
}
83+
}
84+
NSDictionary<NSString *, id> *finalRequestDictionary =
85+
@{ kGIDTokenClaimKeyName: tokenClaimsDictionary };
86+
87+
// === Step 3: Serialize the final dictionary into a JSON string ===
88+
return [_jsonSerializer stringWithJSONObject:finalRequestDictionary error:error];
89+
}
90+
91+
@end

GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,14 @@ typedef NS_ERROR_ENUM(kGIDSignInErrorDomain, GIDSignInErrorCode) {
4545
kGIDSignInErrorCodeCanceled = -5,
4646
/// Indicates an Enterprise Mobility Management related error has occurred.
4747
kGIDSignInErrorCodeEMM = -6,
48+
/// Indicates a claim was requested as both essential and non-essential .
49+
kGIDSignInErrorCodeAmbiguousClaims = -7,
4850
/// Indicates the requested scopes have already been granted to the `currentUser`.
4951
kGIDSignInErrorCodeScopesAlreadyGranted = -8,
5052
/// Indicates there is an operation on a previous user.
5153
kGIDSignInErrorCodeMismatchWithCurrentUser = -9,
54+
/// Indicates that an object could not be serialized into a `JSON` string.
55+
kGIDSignInErrorCodeJSONSerializationFailure = -10
5256
};
5357

5458
/// This class is used to sign in users with their Google account and manage their session.

0 commit comments

Comments
 (0)