Skip to content

Commit

Permalink
Merge pull request #312 from Esri/v.next
Browse files Browse the repository at this point in the history
V.next
  • Loading branch information
mhdostal authored Apr 19, 2023
2 parents fb8b3e4 + 45e66fe commit 92bf62f
Show file tree
Hide file tree
Showing 81 changed files with 1,704 additions and 1,280 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
21B31E8B29EF53BE00A40B10 /* AuthenticationExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AuthenticationExample.entitlements; sourceTree = "<group>"; };
88AD13752834355000500B2E /* AuthenticationExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AuthenticationExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
88AD13782834355000500B2E /* AuthenticationApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationApp.swift; sourceTree = "<group>"; };
88AD137A2834355000500B2E /* SigninView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SigninView.swift; sourceTree = "<group>"; };
88AD137C2834355100500B2E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
88AD137F2834355100500B2E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
88AD13862834355C00500B2E /* arcgis-maps-sdk-swift-toolit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "arcgis-maps-sdk-swift-toolit"; path = ..; sourceTree = "<group>"; };
88AD13862834355C00500B2E /* arcgis-maps-sdk-swift-toolkit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "arcgis-maps-sdk-swift-toolkit"; path = ..; sourceTree = "<group>"; };
88AD1387283435EA00500B2E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
88AD138D283443F800500B2E /* MapItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapItemView.swift; sourceTree = "<group>"; };
88D24DEF288F062D007A539C /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
Expand All @@ -51,7 +52,7 @@
88AD136C2834355000500B2E = {
isa = PBXGroup;
children = (
88AD13862834355C00500B2E /* arcgis-maps-sdk-swift-toolit */,
88AD13862834355C00500B2E /* arcgis-maps-sdk-swift-toolkit */,
88AD13772834355000500B2E /* AuthenticationExample */,
88AD13762834355000500B2E /* Products */,
88AD13882834366100500B2E /* Frameworks */,
Expand All @@ -69,6 +70,7 @@
88AD13772834355000500B2E /* AuthenticationExample */ = {
isa = PBXGroup;
children = (
21B31E8B29EF53BE00A40B10 /* AuthenticationExample.entitlements */,
88AD1387283435EA00500B2E /* Info.plist */,
88AD13782834355000500B2E /* AuthenticationApp.swift */,
88D24DF1288F2FA1007A539C /* HomeView.swift */,
Expand Down Expand Up @@ -237,7 +239,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -292,7 +294,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
Expand All @@ -307,6 +309,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = AuthenticationExample/AuthenticationExample.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"AuthenticationExample/Preview Content\"";
Expand All @@ -322,9 +325,12 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 200.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.esri.Authentication;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
Expand All @@ -336,6 +342,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = AuthenticationExample/AuthenticationExample.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"AuthenticationExample/Preview Content\"";
Expand All @@ -351,9 +358,12 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 200.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.esri.Authentication;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ struct AuthenticationApp: App {
// Create an authenticator.
authenticator = Authenticator(
// If you want to use OAuth, uncomment this code:
//oAuthConfigurations: [.arcgisDotCom]
//oAuthUserConfigurations: [.arcgisDotCom]
)
// Set the challenge handler to be the authenticator we just created.
ArcGISEnvironment.authenticationChallengeHandler = authenticator
// Sets authenticator as ArcGIS and Network challenge handlers to handle authentication
// challenges.
ArcGISEnvironment.authenticationManager.handleChallenges(using: authenticator)
}

var body: some SwiftUI.Scene {
Expand All @@ -49,21 +50,21 @@ struct AuthenticationApp: App {
.environmentObject(authenticator)
.task {
isSettingUp = true
// Here we make the authenticator persistent, which means that it will synchronize
// with they keychain for storing credentials.
// Here we setup credential stores to be persistent, which means that it will
// synchronize with the keychain for storing credentials.
// It also means that a user can sign in without having to be prompted for
// credentials. Once credentials are cleared from the stores ("sign-out"),
// then the user will need to be prompted once again.
try? await authenticator.setupPersistentCredentialStorage(access: .whenUnlockedThisDeviceOnly)
try? await ArcGISEnvironment.authenticationManager.setupPersistentCredentialStorage(access: .whenUnlockedThisDeviceOnly)
isSettingUp = false
}
}
}
}

// If you want to use OAuth, you can uncomment this code:
//private extension OAuthConfiguration {
// static let arcgisDotCom = OAuthConfiguration(
//private extension OAuthUserConfiguration {
// static let arcgisDotCom = OAuthUserConfiguration(
// portalURL: .portal,
// clientID: "<#Your client ID goes here#>",
// // Note: You must have the same redirect URL used here
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ struct FeaturedMapsView: View {
if isLoading {
ProgressView()
} else {
List(featuredItems) { item in
List(featuredItems, id: \.id) { item in
NavigationLink {
MapItemView(map: Map(item: item))
} label: {
Expand Down
6 changes: 2 additions & 4 deletions AuthenticationExample/AuthenticationExample/ProfileView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ import ArcGISToolkit

/// A view that displays the profile of a user.
struct ProfileView: View {
/// The authenticator that has been passed through the environment down to the app.
@EnvironmentObject var authenticator: Authenticator

/// The portal that the user is signed in to.
@State var portal: Portal

Expand Down Expand Up @@ -61,7 +58,8 @@ struct ProfileView: View {
func signOut() {
isSigningOut = true
Task {
await authenticator.clearCredentialStores()
await ArcGISEnvironment.authenticationManager.revokeOAuthTokens()
await ArcGISEnvironment.authenticationManager.clearCredentialStores()
isSigningOut = false
signOutAction()
}
Expand Down
45 changes: 8 additions & 37 deletions AuthenticationExample/AuthenticationExample/SignInView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ import ArcGISToolkit
import CryptoKit

/// A view that allows the user to sign in to a portal.
struct SignInView: View {
/// The authenticator which has been passed from the app through the environment.
@EnvironmentObject var authenticator: Authenticator

struct SignInView: View {
/// The error that occurred during an attempt to sign in.
@State var error: Error?

Expand Down Expand Up @@ -61,10 +58,10 @@ struct SignInView: View {
return
}

if let arcGISCredential = await ArcGISEnvironment.credentialStore.credential(for: .portal) {
lastSignedInUser = arcGISCredential.username ?? ""
if let arcGISCredential = ArcGISEnvironment.authenticationManager.arcGISCredentialStore.credential(for: .portal) {
lastSignedInUser = arcGISCredential.username
} else {
let networkCredentials = await ArcGISEnvironment.networkCredentialStore.credentials(forHost: URL.portal.host!)
let networkCredentials = await ArcGISEnvironment.authenticationManager.networkCredentialStore.credentials(forHost: URL.portal.host!)
if !networkCredentials.isEmpty {
lastSignedInUser = networkCredentials.compactMap { credential in
switch credential {
Expand All @@ -74,6 +71,8 @@ struct SignInView: View {
return ""
case .serverTrust:
return nil
@unknown default:
fatalError("Unknown NetworkCredential")
}
}
.first
Expand Down Expand Up @@ -128,41 +127,13 @@ struct SignInView: View {
}
}

private extension ArcGISCredential {
/// The username, if any, associated with this credential.
var username: String? {
get {
switch self {
case .oauth(let credential):
return credential.username
case .token(let credential):
return credential.username
case .staticToken:
return nil
}
}
}
}

private extension Error {
/// Returns a Boolean value indicating whether the error is the result of cancelling an
/// authentication challenge.
var isChallengeCancellationError: Bool {
switch self {
case let error as ArcGISAuthenticationChallenge.Error:
switch error {
case .userCancelled:
return true
default:
return false
}
case let error as OAuthCredential.AuthorizationError:
switch error {
case .userCancelled:
return true
default:
return false
}
case is CancellationError:
return true
case let error as NSError:
return error.domain == NSURLErrorDomain && error.code == -999
default:
Expand Down
2 changes: 2 additions & 0 deletions AuthenticationExample/AuthenticationExample/UserView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ extension PortalUser.Role: CustomStringConvertible {
return "Admin"
case .publisher:
return "Publisher"
@unknown default:
fatalError("Unknown PortalUser.Role")
}
}
}
Expand Down
29 changes: 21 additions & 8 deletions Documentation/Authenticator/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Authenticator

The `Authenticator` is a configurable object that handles authentication challenges. It will display a user interface when network and ArcGIS authentication challenges occur.
The `Authenticator` is a configurable object that handles authentication challenges. It will display a user interface when network and ArcGIS authentication challenges occur.

![image](https://user-images.githubusercontent.com/3998072/203615041-c887d5e3-bb64-469a-a76b-126059329e92.png)

## Features

The `Authenticator` has a view modifier that will display a prompt when the `Authenticator` is asked to handle an authentication challenge. This will handle many different types of authentication, for example:
The `Authenticator` has a view modifier that will display a prompt when the `Authenticator` is asked to handle an authentication challenge. This will handle many different types of authentication, for example:
- ArcGIS authentication (token and OAuth)
- Integrated Windows Authentication (IWA)
- Client Certificate (PKI)
Expand All @@ -23,7 +23,7 @@ The `Authenticator` can be configured to support securely persisting credentials
@ViewBuilder func authenticator(_ authenticator: Authenticator) -> some View
```

To securely store credentials in the keychain, use the following instance method on `Authenticator`:
To securely store credentials in the keychain, use the following extension method of `AuthenticationManager`:

```swift
/// Sets up new credential stores that will be persisted to the keychain.
Expand All @@ -39,6 +39,18 @@ To securely store credentials in the keychain, use the following instance method
) async throws
```

During sign-out, use the following extension methods of `AuthenticationManager`:

```swift
/// Revokes tokens of OAuth user credentials.
func revokeOAuthTokens() async

/// Clears all ArcGIS and network credentials from the respective stores.
/// Note: This sets up new `URLSessions` so that removed network credentials are respected
/// right away.
func clearCredentialStores() async
```

## Behavior:

The Authenticator view modifier will display an alert prompting the user for credentials. If credentials were persisted to the keychain, the Authenticator will use those instead of requiring the user to reenter credentials.
Expand All @@ -56,21 +68,22 @@ init() {
// If you want to use OAuth, uncomment this code:
//oAuthConfigurations: [.arcgisDotCom]
)
// Set the challenge handler to be the authenticator we just created.
ArcGISEnvironment.authenticationChallengeHandler = authenticator
// Sets authenticator as ArcGIS and Network challenge handlers to handle authentication
// challenges.
ArcGISEnvironment.authenticationManager.handleChallenges(using: authenticator)
}

var body: some SwiftUI.Scene {
WindowGroup {
HomeView()
.authenticator(authenticator)
.task {
// Here we make the authenticator persistent, which means that it will synchronize
// with the keychain for storing credentials.
// Here we setup credential stores to be persistent, which means that it will
// synchronize with the keychain for storing credentials.
// It also means that a user can sign in without having to be prompted for
// credentials. Once credentials are cleared from the stores ("sign-out"),
// then the user will need to be prompted once again.
try? await authenticator.setupPersistentCredentialStorage(access: .whenUnlockedThisDeviceOnly)
try? await ArcGISEnvironment.authenticationManager.setupPersistentCredentialStorage(access: .whenUnlockedThisDeviceOnly)
}
}
}
Expand Down
Loading

0 comments on commit 92bf62f

Please sign in to comment.