Skip to content

Commit e873724

Browse files
authored
[iOS SDK] Update current user account result with latest account and id token after refresh (#2346)
* Update the current user account result data * Made user account get access token testable Added unit and e2e tests * PR comments * Fixed test around scopes after adding bundle id for IOS to client * PR comments * Added ConfigTested to validate config after being created Made createRetrieveAccessTokenError and adjusted tests * Reverted issue around UUID
1 parent 5f5979c commit e873724

13 files changed

+819
-113
lines changed

MSAL/MSAL.xcodeproj/project.pbxproj

Lines changed: 86 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//
2+
// Copyright (c) Microsoft Corporation.
3+
// All rights reserved.
4+
//
5+
// This code is licensed under the MIT License.
6+
//
7+
// Permission is hereby granted, free of charge, to any person obtaining a copy
8+
// of this software and associated documentation files(the "Software"), to deal
9+
// in the Software without restriction, including without limitation the rights
10+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
11+
// copies of the Software, and to permit persons to whom the Software is
12+
// furnished to do so, subject to the following conditions :
13+
//
14+
// The above copyright notice and this permission notice shall be included in
15+
// all copies or substantial portions of the Software.
16+
//
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
// THE SOFTWARE.
24+
25+
/// Wrapper class around PublicClientApplication which helps with testability
26+
class MSALNativeAuthSilentTokenProvider: MSALNativeAuthSilentTokenProviding {
27+
28+
private let application: MSALNativeAuthPublicClientApplication?
29+
30+
init(
31+
configuration config: MSALPublicClientApplicationConfig,
32+
challengeTypes: MSALNativeAuthChallengeTypes) throws {
33+
self.application = try? MSALNativeAuthPublicClientApplication(configuration: config, challengeTypes: challengeTypes)
34+
}
35+
36+
func acquireTokenSilent(parameters: MSALSilentTokenParameters,
37+
completionBlock: @escaping MSALNativeAuthSilentTokenResponse) {
38+
application?.acquireTokenSilent(with: parameters) { result, error in
39+
if let result {
40+
let silentTokenResult = MSALNativeAuthSilentTokenResult(result: result)
41+
completionBlock(silentTokenResult, error)
42+
} else {
43+
completionBlock(nil, error)
44+
}
45+
}
46+
}
47+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//
2+
// Copyright (c) Microsoft Corporation.
3+
// All rights reserved.
4+
//
5+
// This code is licensed under the MIT License.
6+
//
7+
// Permission is hereby granted, free of charge, to any person obtaining a copy
8+
// of this software and associated documentation files(the "Software"), to deal
9+
// in the Software without restriction, including without limitation the rights
10+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
11+
// copies of the Software, and to permit persons to whom the Software is
12+
// furnished to do so, subject to the following conditions :
13+
//
14+
// The above copyright notice and this permission notice shall be included in
15+
// all copies or substantial portions of the Software.
16+
//
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
// THE SOFTWARE.
24+
25+
/// Class used to extract infromation from MSALResult required by the UserAccountResult to be updated and returned
26+
/// MSALResult cannot be directly created from Swift
27+
class MSALNativeAuthSilentTokenResult {
28+
let accessTokenResult: MSALNativeAuthTokenResult
29+
let rawIdToken: String?
30+
let account: MSALAccount
31+
let correlationId: UUID
32+
33+
init(result: MSALResult) {
34+
self.rawIdToken = result.idToken
35+
self.account = result.account
36+
self.accessTokenResult = MSALNativeAuthTokenResult(accessToken: result.accessToken,
37+
scopes: result.scopes,
38+
expiresOn: result.expiresOn)
39+
self.correlationId = result.correlationId
40+
}
41+
42+
init (accessTokenResult: MSALNativeAuthTokenResult,
43+
rawIdToken: String?,
44+
account: MSALAccount,
45+
correlationId: UUID) {
46+
self.rawIdToken = rawIdToken
47+
self.account = account
48+
self.accessTokenResult = accessTokenResult
49+
self.correlationId = correlationId
50+
}
51+
}
52+
53+
typealias MSALNativeAuthSilentTokenResponse = (MSALNativeAuthSilentTokenResult?, (any Error)?) -> Void
54+
55+
protocol MSALNativeAuthSilentTokenProviding {
56+
func acquireTokenSilent(parameters: MSALSilentTokenParameters, completionBlock: @escaping MSALNativeAuthSilentTokenResponse)
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// Copyright (c) Microsoft Corporation.
3+
// All rights reserved.
4+
//
5+
// This code is licensed under the MIT License.
6+
//
7+
// Permission is hereby granted, free of charge, to any person obtaining a copy
8+
// of this software and associated documentation files(the "Software"), to deal
9+
// in the Software without restriction, including without limitation the rights
10+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
11+
// copies of the Software, and to permit persons to whom the Software is
12+
// furnished to do so, subject to the following conditions :
13+
//
14+
// The above copyright notice and this permission notice shall be included in
15+
// all copies or substantial portions of the Software.
16+
//
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
// THE SOFTWARE.
24+
25+
protocol MSALNativeAuthSilentTokenProviderBuildable {
26+
func makeSilentTokenProvider(configuration: MSALPublicClientApplicationConfig,
27+
challengeTypes: MSALNativeAuthChallengeTypes) throws -> MSALNativeAuthSilentTokenProviding?
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// Copyright (c) Microsoft Corporation.
3+
// All rights reserved.
4+
//
5+
// This code is licensed under the MIT License.
6+
//
7+
// Permission is hereby granted, free of charge, to any person obtaining a copy
8+
// of this software and associated documentation files(the "Software"), to deal
9+
// in the Software without restriction, including without limitation the rights
10+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
11+
// copies of the Software, and to permit persons to whom the Software is
12+
// furnished to do so, subject to the following conditions :
13+
//
14+
// The above copyright notice and this permission notice shall be included in
15+
// all copies or substantial portions of the Software.
16+
//
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
// THE SOFTWARE.
24+
25+
class MSALNativeAuthSilentTokenProviderFactory: MSALNativeAuthSilentTokenProviderBuildable {
26+
func makeSilentTokenProvider(configuration: MSALPublicClientApplicationConfig,
27+
challengeTypes: MSALNativeAuthChallengeTypes) throws -> MSALNativeAuthSilentTokenProviding? {
28+
return try? MSALNativeAuthSilentTokenProvider(configuration: configuration, challengeTypes: challengeTypes)
29+
}
30+
}

MSAL/src/native_auth/public/MSALNativeAuthUserAccountResult+Internal.swift

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ extension MSALNativeAuthUserAccountResult {
4343
authority: authority)
4444
config.bypassRedirectURIValidation = configuration.redirectUri == nil
4545

46-
guard let client = try? MSALNativeAuthPublicClientApplication(configuration: config, challengeTypes: challengeTypes)
46+
guard let silentTokenProvider = try? silentTokenProviderFactory.makeSilentTokenProvider(configuration: config, challengeTypes: challengeTypes)
4747
else {
4848
MSALLogger.log(
4949
level: .error,
@@ -55,8 +55,7 @@ extension MSALNativeAuthUserAccountResult {
5555
correlationId: correlationId ?? context.correlationId())) }
5656
return
5757
}
58-
59-
client.acquireTokenSilent(with: params) { [weak self] result, error in
58+
silentTokenProvider.acquireTokenSilent(parameters: params) { [weak self] result, error in
6059
guard let self = self else { return }
6160

6261
if let error = error as? NSError {
@@ -67,10 +66,10 @@ extension MSALNativeAuthUserAccountResult {
6766

6867
if let result = result {
6968
let delegateDispatcher = CredentialsDelegateDispatcher(delegate: delegate, telemetryUpdate: nil)
70-
let accessTokenResult = MSALNativeAuthTokenResult(accessToken: result.accessToken,
71-
scopes: result.scopes,
72-
expiresOn: result.expiresOn)
73-
Task { await delegateDispatcher.dispatchAccessTokenRetrieveCompleted(result: accessTokenResult, correlationId: result.correlationId) }
69+
self.rawIdToken = result.rawIdToken
70+
self.account = result.account
71+
Task { await delegateDispatcher.dispatchAccessTokenRetrieveCompleted(result: result.accessTokenResult,
72+
correlationId: result.correlationId) }
7473
return
7574
}
7675

@@ -83,7 +82,7 @@ extension MSALNativeAuthUserAccountResult {
8382
}
8483
}
8584

86-
func createRetrieveAccessTokenError(error: NSError, context: MSALNativeAuthRequestContext) -> RetrieveAccessTokenError {
85+
private func createRetrieveAccessTokenError(error: NSError, context: MSALNativeAuthRequestContext) -> RetrieveAccessTokenError {
8786
if let innerError = error.userInfo[NSUnderlyingErrorKey] as? NSError {
8887
return createRetrieveAccessTokenError(error: innerError, context: context)
8988
}

MSAL/src/native_auth/public/MSALNativeAuthUserAccountResult.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ import Foundation
2929
/// The account object that holds account information.
3030
@objc public var account: MSALAccount
3131

32-
let configuration: MSALNativeAuthConfiguration
33-
private var rawIdToken: String?
32+
internal let configuration: MSALNativeAuthConfiguration
33+
internal var rawIdToken: String?
3434
private let cacheAccessor: MSALNativeAuthCacheInterface
3535
private let inputValidator: MSALNativeAuthInputValidating
36+
internal let silentTokenProviderFactory: MSALNativeAuthSilentTokenProviderBuildable
3637

3738
/// Get the latest ID token for the account.
3839
@objc public var idToken: String? {
@@ -44,13 +45,15 @@ import Foundation
4445
rawIdToken: String?,
4546
configuration: MSALNativeAuthConfiguration,
4647
cacheAccessor: MSALNativeAuthCacheInterface,
47-
inputValidator: MSALNativeAuthInputValidating = MSALNativeAuthInputValidator()
48+
inputValidator: MSALNativeAuthInputValidating = MSALNativeAuthInputValidator(),
49+
silentTokenProviderFactory: MSALNativeAuthSilentTokenProviderBuildable = MSALNativeAuthSilentTokenProviderFactory()
4850
) {
4951
self.account = account
5052
self.rawIdToken = rawIdToken
5153
self.configuration = configuration
5254
self.cacheAccessor = cacheAccessor
5355
self.inputValidator = inputValidator
56+
self.silentTokenProviderFactory = silentTokenProviderFactory
5457
}
5558

5659
/// Removes all the data from the cache.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//
2+
// Copyright (c) Microsoft Corporation.
3+
// All rights reserved.
4+
//
5+
// This code is licensed under the MIT License.
6+
//
7+
// Permission is hereby granted, free of charge, to any person obtaining a copy
8+
// of this software and associated documentation files(the "Software"), to deal
9+
// in the Software without restriction, including without limitation the rights
10+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
11+
// copies of the Software, and to permit persons to whom the Software is
12+
// furnished to do so, subject to the following conditions :
13+
//
14+
// The above copyright notice and this permission notice shall be included in
15+
// all copies or substantial portions of the Software.
16+
//
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
// THE SOFTWARE.
24+
25+
@testable import MSAL
26+
import XCTest
27+
28+
open class CredentialsDelegateSpy: CredentialsDelegate {
29+
30+
private let expectation: XCTestExpectation
31+
private(set) var error: MSAL.RetrieveAccessTokenError?
32+
private(set) var result: MSAL.MSALNativeAuthTokenResult?
33+
private(set) var onAccessTokenRetrieveErrorCalled = false
34+
private(set) var onAccessTokenRetrieveCompletedCalled = false
35+
36+
init(expectation: XCTestExpectation) {
37+
self.expectation = expectation
38+
}
39+
40+
public func onAccessTokenRetrieveCompleted(result: MSALNativeAuthTokenResult) {
41+
onAccessTokenRetrieveCompletedCalled = true
42+
self.result = result
43+
44+
expectation.fulfill()
45+
}
46+
47+
public func onAccessTokenRetrieveError(error: MSAL.RetrieveAccessTokenError) {
48+
onAccessTokenRetrieveErrorCalled = true
49+
self.error = error
50+
51+
expectation.fulfill()
52+
}
53+
}

0 commit comments

Comments
 (0)