Skip to content

Commit 7aee035

Browse files
authored
Add support for claims like auth_time (#569)
1 parent f8dfad9 commit 7aee035

27 files changed

+1426
-45
lines changed

GoogleSignIn/Sources/GIDClaim.m

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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/Public/GoogleSignIn/GIDClaim.h"
18+
19+
NSString * const kAuthTimeClaimName = @"auth_time";
20+
21+
// Private interface to declare the internal initializer
22+
@interface GIDClaim ()
23+
24+
- (instancetype)initWithName:(NSString *)name
25+
essential:(BOOL)essential NS_DESIGNATED_INITIALIZER;
26+
27+
@end
28+
29+
@implementation GIDClaim
30+
31+
// Private designated initializer
32+
- (instancetype)initWithName:(NSString *)name essential:(BOOL)essential {
33+
self = [super init];
34+
if (self) {
35+
_name = [name copy];
36+
_essential = essential;
37+
}
38+
return self;
39+
}
40+
41+
#pragma mark - Factory Methods
42+
43+
+ (instancetype)authTimeClaim {
44+
return [[self alloc] initWithName:kAuthTimeClaimName essential:NO];
45+
}
46+
47+
+ (instancetype)essentialAuthTimeClaim {
48+
return [[self alloc] initWithName:kAuthTimeClaimName essential:YES];
49+
}
50+
51+
#pragma mark - NSObject
52+
53+
- (BOOL)isEqual:(id)object {
54+
// 1. Check if the other object is the same instance in memory.
55+
if (self == object) {
56+
return YES;
57+
}
58+
59+
// 2. Check if the other object is not a GIDTokenClaim instance.
60+
if (![object isKindOfClass:[GIDClaim class]]) {
61+
return NO;
62+
}
63+
64+
// 3. Compare the properties that define equality.
65+
GIDClaim *other = (GIDClaim *)object;
66+
return [self.name isEqualToString:other.name] &&
67+
self.isEssential == other.isEssential;
68+
}
69+
70+
- (NSUInteger)hash {
71+
// The hash value should be based on the same properties used in isEqual:
72+
return self.name.hash ^ @(self.isEssential).hash;
73+
}
74+
75+
@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 GIDClaim;
20+
21+
NS_ASSUME_NONNULL_BEGIN
22+
23+
extern NSString *const kGIDClaimErrorDescription;
24+
extern NSString *const kGIDClaimEssentialPropertyKeyName;
25+
extern NSString *const kGIDClaimKeyName;
26+
27+
@protocol GIDJSONSerializer;
28+
29+
/**
30+
* An internal utility class for processing and serializing the `NSSet` of `GIDClaim` objects
31+
* into the `JSON` format required for an `OIDAuthorizationRequest`.
32+
*/
33+
@interface GIDClaimsInternalOptions : NSObject
34+
35+
- (instancetype)init;
36+
37+
- (instancetype)initWithJSONSerializer:
38+
(id<GIDJSONSerializer>)jsonSerializer NS_DESIGNATED_INITIALIZER;
39+
40+
/**
41+
* Processes the `NSSet` of `GIDClaim` objects, handling ambiguous claims,
42+
* and returns a `JSON` string.
43+
*
44+
* @param claims The `NSSet` of `GIDClaim` 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<GIDClaim *> *)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/GIDClaimsInternalOptions.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/GIDClaim.h"
23+
24+
NSString * const kGIDClaimErrorDescription =
25+
@"The claim was requested as both essential and non-essential. "
26+
@"Please provide only one version.";
27+
NSString * const kGIDClaimEssentialPropertyKey = @"essential";
28+
NSString * const kGIDClaimKeyName = @"id_token";
29+
30+
@interface GIDClaimsInternalOptions ()
31+
@property(nonatomic, readonly) id<GIDJSONSerializer> jsonSerializer;
32+
@end
33+
34+
@implementation GIDClaimsInternalOptions
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<GIDClaim *> *)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 *, GIDClaim *> *validClaims =
55+
[[NSMutableDictionary alloc] init];
56+
57+
for (GIDClaim *currentClaim in claims) {
58+
GIDClaim *existingClaim = validClaims[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:kGIDClaimErrorDescription
67+
}];
68+
}
69+
return nil;
70+
}
71+
validClaims[currentClaim.name] = currentClaim;
72+
}
73+
74+
// === Step 2: Build the dictionary structure required for OIDC JSON ===
75+
NSMutableDictionary<NSString *, NSDictionary *> *claimsDictionary =
76+
[[NSMutableDictionary alloc] init];
77+
for (GIDClaim *claim in validClaims.allValues) {
78+
if (claim.isEssential) {
79+
claimsDictionary[claim.name] = @{ kGIDClaimEssentialPropertyKey: @YES };
80+
} else {
81+
claimsDictionary[claim.name] = @{ kGIDClaimEssentialPropertyKey: @NO };
82+
}
83+
}
84+
NSDictionary<NSString *, id> *finalRequestDictionary =
85+
@{ kGIDClaimKeyName: claimsDictionary };
86+
87+
// === Step 3: Serialize the final dictionary into a JSON string ===
88+
return [_jsonSerializer stringWithJSONObject:finalRequestDictionary error:error];
89+
}
90+
91+
@end
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

0 commit comments

Comments
 (0)