Skip to content

Commit a0ec7cc

Browse files
authored
Merge pull request #289 from ForgeRock/develop
ForgeRock iOS SDK 4.5.0 Release
2 parents 94dd1b2 + b954e2b commit a0ec7cc

File tree

44 files changed

+1643
-844
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1643
-844
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
## [4.5.0]
2+
#### Added
3+
- Added SDK support for deleting registered WebAuthn devices from the server. [SDKS-1753]
4+
- Added support for signing off from PingOne to the centralized login flow. [SDKS-3021]
5+
- Added the ability to dynamically configure the SDK by collecting values from the server's OpenID Connect `.well-known` endpoint. [SDKS-3023]
6+
7+
#### Fixed
8+
- SSL pinning configuration was ignored in `FRURLProtocol` class. [SDKS-3239]
9+
- Removed scope validation from `AccessToken` initialization. [SDKS-3305]
10+
111
## [4.4.1]
212
#### Added
313
- Added privacy manifest files to the SDK's modules [SDKS-3086]

FRAuth.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
Pod::Spec.new do |s|
1010
s.name = 'FRAuth'
11-
s.version = '4.4.1'
11+
s.version = '4.5.0'
1212
s.summary = 'ForgeRock Auth SDK for iOS'
1313
s.description = <<-DESC
1414
FRAuth is a SDK that allows you easily and quickly develop an application with ForgeRock Platform or ForgeRock Identity Cloud. FRAuth SDK provides interfaces and functionalities of user authentication, registration, and identity and access management against ForgeRock solutions.
@@ -32,5 +32,5 @@ Pod::Spec.new do |s|
3232
s.resource_bundles = {
3333
'FRAuth' => [base_dir + '/*.xcprivacy']
3434
}
35-
s.ios.dependency 'FRCore', '~> 4.4.1'
35+
s.ios.dependency 'FRCore', '~> 4.5.0'
3636
end

FRAuth/FRAuth.xcodeproj/project.pbxproj

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
1B5DD69B2BF599F400EE0C8B /* RemoteWebAuthnRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5DD69A2BF599F400EE0C8B /* RemoteWebAuthnRepository.swift */; };
1011
1B6037E62B505924009090DF /* TextInputCallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6037E52B505924009090DF /* TextInputCallback.swift */; };
1112
1B85DA132B85208C0023E953 /* TextInputCallbackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B85DA122B85208C0023E953 /* TextInputCallbackTests.swift */; };
1213
3A1B43D0284510B700EAFC9D /* AtomicDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A1B43CF284510B700EAFC9D /* AtomicDictionary.swift */; };
@@ -339,6 +340,7 @@
339340
/* End PBXContainerItemProxy section */
340341

341342
/* Begin PBXFileReference section */
343+
1B5DD69A2BF599F400EE0C8B /* RemoteWebAuthnRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteWebAuthnRepository.swift; sourceTree = "<group>"; };
342344
1B6037E52B505924009090DF /* TextInputCallback.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextInputCallback.swift; sourceTree = "<group>"; };
343345
1B85DA122B85208C0023E953 /* TextInputCallbackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextInputCallbackTests.swift; sourceTree = "<group>"; };
344346
3A1B43CF284510B700EAFC9D /* AtomicDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicDictionary.swift; sourceTree = "<group>"; };
@@ -852,6 +854,7 @@
852854
D53A8038262789BD0093B1CA /* WAKTypes.swift */,
853855
ECDF5F4829674E9E007BB721 /* FRWebAuthn.swift */,
854856
EC13ABA929A380920069AC41 /* FRWebAuthnManager.swift */,
857+
1B5DD69A2BF599F400EE0C8B /* RemoteWebAuthnRepository.swift */,
855858
);
856859
path = WebAuthn;
857860
sourceTree = "<group>";
@@ -1825,6 +1828,7 @@
18251828
D586CFB423358EE0007A2194 /* FRDeviceCollector.swift in Sources */,
18261829
D53A804C262789BD0093B1CA /* ClientGetOperation.swift in Sources */,
18271830
D586CF9423358EE0007A2194 /* PasswordCallback.swift in Sources */,
1831+
1B5DD69B2BF599F400EE0C8B /* RemoteWebAuthnRepository.swift in Sources */,
18281832
D586CFBF23358EE0007A2194 /* TokenManager.swift in Sources */,
18291833
D586CFA923358EE0007A2194 /* OAuth2Client.swift in Sources */,
18301834
EC0BA2E7285B8F8F00F8326E /* FROptions.swift in Sources */,
@@ -2146,7 +2150,7 @@
21462150
"@executable_path/Frameworks",
21472151
"@loader_path/Frameworks",
21482152
);
2149-
MARKETING_VERSION = 4.4.1;
2153+
MARKETING_VERSION = 4.5.0;
21502154
MODULEMAP_FILE = "";
21512155
OTHER_CFLAGS = "-DXCODE_FRAMEWORK=1";
21522156
PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.FRAuth;
@@ -2183,7 +2187,7 @@
21832187
"@executable_path/Frameworks",
21842188
"@loader_path/Frameworks",
21852189
);
2186-
MARKETING_VERSION = 4.4.1;
2190+
MARKETING_VERSION = 4.5.0;
21872191
MODULEMAP_FILE = "";
21882192
OTHER_CFLAGS = "-DXCODE_FRAMEWORK=1";
21892193
PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.FRAuth;

FRAuth/FRAuth/Config/FROptions.swift

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// FROptions.swift
33
// FRAuth
44
//
5-
// Copyright (c) 2022 ForgeRock. All rights reserved.
5+
// Copyright (c) 2022-2024 ForgeRock. All rights reserved.
66
//
77
// This software may be modified and distributed under the terms
88
// of the MIT license. See the LICENSE file for details.
@@ -13,7 +13,7 @@ import Foundation
1313
/// FROptions represents a configuration object for the SDK. It can be used for passing configuration options in the FRAuth.start() method.
1414
///
1515
@objc
16-
public class FROptions: NSObject, Codable {
16+
open class FROptions: NSObject, Codable {
1717
/// String constant for FROptions storage key
1818
internal static let frOptionsStorageKey: String = "FROptions"
1919

@@ -37,6 +37,7 @@ public class FROptions: NSObject, Codable {
3737
public var oauthThreshold: String?
3838
public var oauthClientId: String?
3939
public var oauthRedirectUri: String?
40+
public var oauthSignoutRedirectUri: String?
4041
public var oauthScope: String?
4142
public var keychainAccessGroup: String?
4243
public var sslPinningPublicKeyHashes: [String]?
@@ -59,6 +60,7 @@ public class FROptions: NSObject, Codable {
5960
case oauthThreshold = "forgerock_oauth_threshold"
6061
case oauthClientId = "forgerock_oauth_client_id"
6162
case oauthRedirectUri = "forgerock_oauth_redirect_uri"
63+
case oauthSignoutRedirectUri = "forgerock_oauth_sign_out_redirect_uri"
6264
case oauthScope = "forgerock_oauth_scope"
6365
case keychainAccessGroup = "forgerock_keychain_access_group"
6466
case sslPinningPublicKeyHashes = "forgerock_ssl_pinning_public_key_hashes"
@@ -84,6 +86,7 @@ public class FROptions: NSObject, Codable {
8486
/// - oauthThreshold: OAuth Client timeout threshold
8587
/// - oauthClientId: OAuth Client name
8688
/// - oauthRedirectUri: OAuth Client redirectURI
89+
/// - oauthSignoutRedirectUri: OAuth Client signout redirectURI
8790
/// - oauthScope: OAuth Client scopes
8891
/// - keychainAccessGroup: Keychain access group for shared keychain
8992
/// - sslPinningPublicKeyHashes: SSL Pinning hashes
@@ -105,6 +108,7 @@ public class FROptions: NSObject, Codable {
105108
oauthThreshold: String? = nil,
106109
oauthClientId: String? = nil,
107110
oauthRedirectUri: String? = nil,
111+
oauthSignoutRedirectUri: String? = nil,
108112
oauthScope: String? = nil,
109113
keychainAccessGroup: String? = nil,
110114
sslPinningPublicKeyHashes: [String]? = nil) {
@@ -125,6 +129,7 @@ public class FROptions: NSObject, Codable {
125129
self.oauthClientId = oauthClientId
126130
self.oauthThreshold = oauthThreshold
127131
self.oauthRedirectUri = oauthRedirectUri
132+
self.oauthSignoutRedirectUri = oauthSignoutRedirectUri
128133
self.oauthScope = oauthScope
129134
self.keychainAccessGroup = keychainAccessGroup
130135
self.sslPinningPublicKeyHashes = sslPinningPublicKeyHashes
@@ -154,6 +159,7 @@ public class FROptions: NSObject, Codable {
154159
self.oauthClientId = config[FROptions.CodingKeys.oauthClientId.rawValue] as? String
155160
self.oauthThreshold = config[FROptions.CodingKeys.oauthThreshold.rawValue] as? String
156161
self.oauthRedirectUri = config[FROptions.CodingKeys.oauthRedirectUri.rawValue] as? String
162+
self.oauthSignoutRedirectUri = config[FROptions.CodingKeys.oauthSignoutRedirectUri.rawValue] as? String
157163
self.oauthScope = config[FROptions.CodingKeys.oauthScope.rawValue] as? String
158164
self.keychainAccessGroup = config[FROptions.CodingKeys.keychainAccessGroup.rawValue] as? String
159165
self.sslPinningPublicKeyHashes = config[FROptions.CodingKeys.sslPinningPublicKeyHashes.rawValue] as? [String]
@@ -195,7 +201,32 @@ public class FROptions: NSObject, Codable {
195201
public func getEndSessionEndpoint() -> String {
196202
return self.endSessionEndpoint ?? "/oauth2/realms/\(self.realm)/connect/endSession"
197203
}
198-
204+
205+
/// Asynchronously discovers configuration options based on a provided discovery URL.
206+
///
207+
/// - Parameter discoveryURL: The URL string from which to discover configuration options. This URL should point to a well-known configuration endpoint that returns the necessary configuration settings in a JSON format.
208+
/// - Returns: An instance of `FROptions` populated with the configuration settings fetched from the discovery URL.
209+
@available(iOS 13.0.0, *)
210+
open func discover(discoveryURL: String) async throws -> FROptions {
211+
guard let discoveryURL = URL(string: discoveryURL) else {
212+
throw OAuth2Error.other("Invalid discovery URL")
213+
}
214+
let data = try await URLSession.shared.data(from: discoveryURL)
215+
let config = try JSONDecoder().decode(OpenIdConfiguration.self, from: data.0)
216+
217+
guard let baseUrl = self.url.isEmpty ? config.issuer : self.url else {
218+
throw OAuth2Error.other("Missing base URL")
219+
}
220+
self.url = baseUrl
221+
self.authorizeEndpoint = config.authorizationEndpoint
222+
self.tokenEndpoint = config.tokenEndpoint
223+
self.userinfoEndpoint = config.userinfoEndpoint
224+
self.endSessionEndpoint = config.endSessionEndpoint
225+
self.revokeEndpoint = config.revocationEndpoint
226+
227+
return self
228+
}
229+
199230
// - MARK: Private
200231

201232
/// Equatable comparison method. Comparing the realm, cookie and oauthClientId values
@@ -206,6 +237,7 @@ public class FROptions: NSObject, Codable {
206237
lhs.oauthClientId == rhs.oauthClientId &&
207238
lhs.oauthScope == rhs.oauthScope &&
208239
lhs.oauthRedirectUri == rhs.oauthRedirectUri &&
240+
lhs.oauthSignoutRedirectUri == rhs.oauthSignoutRedirectUri &&
209241
lhs.keychainAccessGroup == rhs.keychainAccessGroup)
210242
}
211243
}
@@ -219,3 +251,23 @@ extension Encodable {
219251
return dictionary
220252
}
221253
}
254+
255+
private struct OpenIdConfiguration: Codable {
256+
public let issuer: String?
257+
public let authorizationEndpoint: String?
258+
public let tokenEndpoint: String?
259+
public let userinfoEndpoint: String?
260+
public let endSessionEndpoint: String?
261+
public let revocationEndpoint: String?
262+
263+
264+
private enum CodingKeys: String, CodingKey {
265+
case issuer = "issuer"
266+
case authorizationEndpoint = "authorization_endpoint"
267+
case tokenEndpoint = "token_endpoint"
268+
case userinfoEndpoint = "userinfo_endpoint"
269+
case endSessionEndpoint = "end_session_endpoint"
270+
case revocationEndpoint = "revocation_endpoint"
271+
}
272+
}
273+

FRAuth/FRAuth/Config/OAuth2Client.swift

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// OAuth2Client.swift
33
// FRAuth
44
//
5-
// Copyright (c) 2019-2023 ForgeRock. All rights reserved.
5+
// Copyright (c) 2019-2024 ForgeRock. All rights reserved.
66
//
77
// This software may be modified and distributed under the terms
88
// of the MIT license. See the LICENSE file for details.
@@ -23,6 +23,8 @@ public class OAuth2Client: NSObject, Codable {
2323
let scope: String
2424
/// OAuth2 redirect_uri for the client
2525
let redirectUri: URL
26+
/// OAuth2 signout_redirect_uri for the client
27+
let signoutRedirectUri: URL?
2628
/// ServerConfig which OAuth2 client will communicate to
2729
let serverConfig: ServerConfig
2830
/// Threshold to refresh access_token in advance
@@ -37,13 +39,15 @@ public class OAuth2Client: NSObject, Codable {
3739
/// - clientId: client_id of the client
3840
/// - scope: set of scope(s) separated by space to request for the client; requesting scope set must be registered in the OAuth2 client
3941
/// - redirectUri: redirect_uri in URL object as registered in the client
42+
/// - signoutRedirectUri: optional signout_redirect_uri in URL object as registered in the client
4043
/// - serverConfig: ServerConfig that OAuth2 Client will communicate to
4144
/// - threshold: threshold in seconds to refresh access_token before it actually expires
4245
@objc
43-
public init (clientId: String, scope: String, redirectUri: URL, serverConfig: ServerConfig, threshold: Int = 60) {
44-
46+
public init (clientId: String, scope: String, redirectUri: URL, signoutRedirectUri: URL? = nil, serverConfig: ServerConfig, threshold: Int = 60) {
47+
4548
self.clientId = clientId
4649
self.redirectUri = redirectUri
50+
self.signoutRedirectUri = signoutRedirectUri
4751
self.scope = scope
4852
self.serverConfig = serverConfig
4953
self.threshold = threshold
@@ -458,4 +462,30 @@ public class OAuth2Client: NSObject, Codable {
458462
// Call /token service to exchange auth code to OAuth token set
459463
return Request(url: self.serverConfig.tokenURL, method: .POST, headers: header, bodyParams: parameter, requestType: .urlEncoded, responseType: .json, timeoutInterval: self.serverConfig.timeout)
460464
}
465+
466+
/// Builds /endSession request for an external user-agent based on given OAuth2 client information
467+
/// - Parameters:
468+
/// - idToken: OIDC id_token
469+
/// - Returns: Request object
470+
func buildEndSessionRequestForExternalAgent(idToken: String?) -> Request {
471+
// Construct parameter for the request
472+
var parameter: [String: String] = [:]
473+
if let signoutRedirectUri = self.signoutRedirectUri {
474+
parameter[OAuth2.postLogoutRedirectUri] = signoutRedirectUri.absoluteString
475+
}
476+
if let idToken, !idToken.isEmpty {
477+
parameter[OAuth2.idTokenHint] = idToken
478+
}
479+
480+
// AM 6.5.2 - 7.0.0
481+
//
482+
// Endpoint: /oauth2/realms/endSession
483+
// API Version: resource=2.1,protocol=1.0
484+
485+
var header: [String: String] = [:]
486+
header[OpenAM.acceptAPIVersion] = OpenAM.apiResource21 + "," + OpenAM.apiProtocol10
487+
488+
return Request(url: self.serverConfig.endSessionURL, method: .GET, headers: header, urlParams:parameter, requestType: .urlEncoded, responseType: .urlEncoded, timeoutInterval: self.serverConfig.timeout)
489+
}
490+
461491
}

FRAuth/FRAuth/Config/ServerConfig.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// ServerConfig.swift
33
// FRAuth
44
//
5-
// Copyright (c) 2019-2020 ForgeRock. All rights reserved.
5+
// Copyright (c) 2019-2024 ForgeRock. All rights reserved.
66
//
77
// This software may be modified and distributed under the terms
88
// of the MIT license. See the LICENSE file for details.
@@ -95,43 +95,43 @@ public class ServerConfigBuilder: NSObject {
9595

9696
@objc
9797
@discardableResult public func set(authenticatePath: String) -> ServerConfigBuilder {
98-
self.config.authenticateURL = self.config.baseURL.absoluteString + authenticatePath
98+
self.config.authenticateURL = authenticatePath.isValidUrl ? authenticatePath : self.config.baseURL.absoluteString + authenticatePath
9999
return self
100100
}
101101

102102
@objc
103103
@discardableResult public func set(tokenPath: String) -> ServerConfigBuilder {
104-
self.config.tokenURL = self.config.baseURL.absoluteString + tokenPath
104+
self.config.tokenURL = tokenPath.isValidUrl ? tokenPath : self.config.baseURL.absoluteString + tokenPath
105105
return self
106106
}
107107

108108
@objc
109109
@discardableResult public func set(authorizePath: String) -> ServerConfigBuilder {
110-
self.config.authorizeURL = self.config.baseURL.absoluteString + authorizePath
110+
self.config.authorizeURL = authorizePath.isValidUrl ? authorizePath : self.config.baseURL.absoluteString + authorizePath
111111
return self
112112
}
113113

114114
@objc
115115
@discardableResult public func set(userInfoPath: String) -> ServerConfigBuilder {
116-
self.config.userInfoURL = self.config.baseURL.absoluteString + userInfoPath
116+
self.config.userInfoURL = userInfoPath.isValidUrl ? userInfoPath : self.config.baseURL.absoluteString + userInfoPath
117117
return self
118118
}
119119

120120
@objc
121121
@discardableResult public func set(revokePath: String) -> ServerConfigBuilder {
122-
self.config.tokenRevokeURL = self.config.baseURL.absoluteString + revokePath
122+
self.config.tokenRevokeURL = revokePath.isValidUrl ? revokePath : self.config.baseURL.absoluteString + revokePath
123123
return self
124124
}
125125

126126
@objc
127127
@discardableResult public func set(sessionPath: String) -> ServerConfigBuilder {
128-
self.config.sessionURL = self.config.baseURL.absoluteString + sessionPath
128+
self.config.sessionURL = sessionPath.isValidUrl ? sessionPath : self.config.baseURL.absoluteString + sessionPath
129129
return self
130130
}
131131

132132
@objc
133133
@discardableResult public func set(endSessionPath: String) -> ServerConfigBuilder {
134-
self.config.endSessionURL = self.config.baseURL.absoluteString + endSessionPath
134+
self.config.endSessionURL = endSessionPath.isValidUrl ? endSessionPath : self.config.baseURL.absoluteString + endSessionPath
135135
return self
136136
}
137137

FRAuth/FRAuth/Constants/OAuth2.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// OAuth2.swift
33
// FRAuth
44
//
5-
// Copyright (c) 2019-2020 ForgeRock. All rights reserved.
5+
// Copyright (c) 2019-2024 ForgeRock. All rights reserved.
66
//
77
// This software may be modified and distributed under the terms
88
// of the MIT license. See the LICENSE file for details.
@@ -13,7 +13,8 @@ struct OAuth2 {
1313
static let clientId = "client_id"
1414
static let scope = "scope"
1515
static let redirecUri = "redirect_uri"
16-
16+
static let postLogoutRedirectUri = "post_logout_redirect_uri"
17+
1718
static let csrf = "csrf"
1819
static let decision = "decision"
1920

FRAuth/FRAuth/FRAuth.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// FRAuth.swift
33
// FRAuth
44
//
5-
// Copyright (c) 2019-2023 ForgeRock. All rights reserved.
5+
// Copyright (c) 2019-2024 ForgeRock. All rights reserved.
66
//
77
// This software may be modified and distributed under the terms
88
// of the MIT license. See the LICENSE file for details.
@@ -224,7 +224,9 @@ public final class FRAuth: NSObject {
224224
if let thresholdConfigStr = config[FROptions.CodingKeys.oauthThreshold.rawValue] as? String, let timeOutConfigInt = Int(thresholdConfigStr) {
225225
threshold = timeOutConfigInt
226226
}
227-
227+
228+
let signoutRedirectUri = URL(string: config[FROptions.CodingKeys.oauthSignoutRedirectUri.rawValue] as? String ?? "")
229+
228230
let serverConfig = configBuilder.build()
229231
FRLog.v("ServerConfig created: \(serverConfig)")
230232
var oAuth2Client: OAuth2Client?
@@ -235,7 +237,7 @@ public final class FRAuth: NSObject {
235237
redirectUri.absoluteString.isValidUrl,
236238
let scope = config[FROptions.CodingKeys.oauthScope.rawValue] as? String
237239
{
238-
oAuth2Client = OAuth2Client(clientId: clientId, scope: scope, redirectUri: redirectUri, serverConfig: serverConfig, threshold: threshold)
240+
oAuth2Client = OAuth2Client(clientId: clientId, scope: scope, redirectUri: redirectUri, signoutRedirectUri: signoutRedirectUri, serverConfig: serverConfig, threshold: threshold)
239241
FRLog.v("OAuth2Client created: \(String(describing: oAuth2Client))")
240242
}
241243
else {

0 commit comments

Comments
 (0)