Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 17 additions & 2 deletions iOS/swiftui-davinci/Davinci/Davinci.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
A5EED25C2CF7B5480064B168 /* UserInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EED25B2CF7B5480064B168 /* UserInfoView.swift */; };
A5EED25E2CF7B8F80064B168 /* UserInfoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EED25D2CF7B8F80064B168 /* UserInfoViewModel.swift */; };
A5EED2602CF7B9380064B168 /* LogOutViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EED25F2CF7B9380064B168 /* LogOutViewModel.swift */; };
B1OIDC012024ABCD0001 /* OidcLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1OIDC022024ABCD0002 /* OidcLoginView.swift */; };
B1OIDC032024ABCD0003 /* OidcLoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1OIDC042024ABCD0004 /* OidcLoginViewModel.swift */; };
EC83DE312E5F2BD600D2B5B4 /* DeviceRegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC83DE2F2E5F2BD600D2B5B4 /* DeviceRegistrationView.swift */; };
EC83DE322E5F2BD600D2B5B4 /* DeviceAuthenticationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC83DE2E2E5F2BD600D2B5B4 /* DeviceAuthenticationView.swift */; };
EC83DE332E5F2BD600D2B5B4 /* PhoneNumberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC83DE302E5F2BD600D2B5B4 /* PhoneNumberView.swift */; };
Expand All @@ -66,6 +68,7 @@
EC83DE5A2E5F2D1D00D2B5B4 /* SVGKit in Frameworks */ = {isa = PBXBuildFile; productRef = EC83DE592E5F2D1D00D2B5B4 /* SVGKit */; };
EC83DE5C2E5F2D1D00D2B5B4 /* SVGKitSwift in Frameworks */ = {isa = PBXBuildFile; productRef = EC83DE5B2E5F2D1D00D2B5B4 /* SVGKitSwift */; };
EC83DE5E2E5F2EFE00D2B5B4 /* AsyncSVGImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC83DE5D2E5F2EFE00D2B5B4 /* AsyncSVGImage.swift */; };
ECCC647B2E783FCB00CD9650 /* PingJourney in Frameworks */ = {isa = PBXBuildFile; productRef = ECCC647A2E783FCB00CD9650 /* PingJourney */; };
/* End PBXBuildFile section */

/* Begin PBXCopyFilesBuildPhase section */
Expand Down Expand Up @@ -110,6 +113,8 @@
A5EED25B2CF7B5480064B168 /* UserInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoView.swift; sourceTree = "<group>"; };
A5EED25D2CF7B8F80064B168 /* UserInfoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoViewModel.swift; sourceTree = "<group>"; };
A5EED25F2CF7B9380064B168 /* LogOutViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogOutViewModel.swift; sourceTree = "<group>"; };
B1OIDC022024ABCD0002 /* OidcLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OidcLoginView.swift; sourceTree = "<group>"; };
B1OIDC042024ABCD0004 /* OidcLoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OidcLoginViewModel.swift; sourceTree = "<group>"; };
EC83DE2E2E5F2BD600D2B5B4 /* DeviceAuthenticationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceAuthenticationView.swift; sourceTree = "<group>"; };
EC83DE2F2E5F2BD600D2B5B4 /* DeviceRegistrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRegistrationView.swift; sourceTree = "<group>"; };
EC83DE302E5F2BD600D2B5B4 /* PhoneNumberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberView.swift; sourceTree = "<group>"; };
Expand All @@ -133,6 +138,7 @@
A52877782D9C71C400B4885D /* PingBrowser in Frameworks */,
A51C18EC2CF5247200A78241 /* PingOidc in Frameworks */,
EC83DE5C2E5F2D1D00D2B5B4 /* SVGKitSwift in Frameworks */,
ECCC647B2E783FCB00CD9650 /* PingJourney in Frameworks */,
A51313D32D9C6C640086F846 /* PingLogger in Frameworks */,
A52877822D9C71C400B4885D /* PingOrchestrate in Frameworks */,
EC83DE372E5F2CA200D2B5B4 /* PingExternalIdPApple in Frameworks */,
Expand Down Expand Up @@ -193,6 +199,8 @@
A5EED25F2CF7B9380064B168 /* LogOutViewModel.swift */,
A5EED25B2CF7B5480064B168 /* UserInfoView.swift */,
A5EED25D2CF7B8F80064B168 /* UserInfoViewModel.swift */,
B1OIDC022024ABCD0002 /* OidcLoginView.swift */,
B1OIDC042024ABCD0004 /* OidcLoginViewModel.swift */,
3A5441342BCDF10B00385131 /* Assets.xcassets */,
3A5441362BCDF10B00385131 /* Preview Content */,
);
Expand Down Expand Up @@ -339,6 +347,8 @@
A5A5F8612D5CE16A0053BF24 /* TextView.swift in Sources */,
3A3709962BEB038200AA7B51 /* AccessTokenView.swift in Sources */,
A5A5F8652D5CE4A60053BF24 /* ErrorView.swift in Sources */,
B1OIDC012024ABCD0001 /* OidcLoginView.swift in Sources */,
B1OIDC032024ABCD0003 /* OidcLoginViewModel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -584,8 +594,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/ForgeRock/ping-ios-sdk";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.2.0;
branch = "SDKS-4353_OIDC_Bug_fixes";
kind = branch;
};
};
EC83DE582E5F2D1D00D2B5B4 /* XCRemoteSwiftPackageReference "SVGKit" */ = {
Expand Down Expand Up @@ -744,6 +754,11 @@
package = EC83DE582E5F2D1D00D2B5B4 /* XCRemoteSwiftPackageReference "SVGKit" */;
productName = SVGKitSwift;
};
ECCC647A2E783FCB00CD9650 /* PingJourney */ = {
isa = XCSwiftPackageProductDependency;
package = A57584782D9DD9EC00AFD754 /* XCRemoteSwiftPackageReference "ping-ios-sdk" */;
productName = PingJourney;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 3A5441252BCDF10900385131 /* Project object */;
Expand Down
33 changes: 24 additions & 9 deletions iOS/swiftui-davinci/Davinci/Davinci/AccessTokenViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

import Foundation
import PingLogger
import PingOrchestrate
import PingOidc

/// A view model responsible for managing the access token state.
/// - This class handles fetching the access token using the DaVinci SDK and logs the results.
Expand All @@ -29,19 +31,29 @@ class AccessTokenViewModel: ObservableObject {
}
}

/// Fetches the access token using the DaVinci SDK.
/// - The method checks for a successful token retrieval and updates the `accessToken` property.
/// Fetches the access token using either the DaVinci SDK or OIDC Web SDK.
/// - The method checks both daVinci and oidcLogin for an authenticated user and retrieves their token.
/// - Logs the success or failure result using `PingLogger`.
func accessToken() async {
/// Request the token from the DaVinci SDK
let token = await davinci.user()?.token()
var tokenResult: Result<Token, OidcError>?

/// Check DaVinci user first
if let daVinciUser = await daVinci.daVinciUser() {
tokenResult = await daVinciUser.token()
LogManager.standard.i("Fetching token from DaVinci user")
}
/// If no DaVinci user, check OIDC user
else if let oidcUser = await oidcLogin.user() {
tokenResult = await oidcUser.token()
LogManager.standard.i("Fetching token from OIDC user")
}

/// Process the token result
switch token {
case .success(let accessToken):
switch tokenResult {
case .success(let token):
/// Update the UI on the main thread with the received token
await MainActor.run {
self.accessToken = String(describing: accessToken)
self.accessToken = String(describing: token.accessToken)
}
/// Log the successful token retrieval
LogManager.standard.i("AccessToken: \(self.accessToken)")
Expand All @@ -53,8 +65,11 @@ class AccessTokenViewModel: ObservableObject {
/// Log the error that occurred
LogManager.standard.e("", error: error)
case .none:
/// No response received, no further action required
break
/// No authenticated user found in either DaVinci or OIDC
await MainActor.run {
self.accessToken = "No authenticated user found"
}
LogManager.standard.i("No authenticated user found in either DaVinci or OIDC")
}
}
}
7 changes: 7 additions & 0 deletions iOS/swiftui-davinci/Davinci/Davinci/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ struct ContentView: View {
NavigationLink(value: "DaVinci") {
Text("Launch DaVinci")
}
/// Navigation option to launch OIDC login.
/// - Takes the user to the OIDC Web authentication flow.
NavigationLink(value: "OIDC") {
Text("Launch OIDC Login")
}
/// Navigation option to access the token-related view.
/// - Shows the user's current access token.
NavigationLink(value: "Token") {
Expand All @@ -62,6 +67,8 @@ struct ContentView: View {
switch item {
case "DaVinci":
DavinciView(path: $path)
case "OIDC":
OidcLoginView(path: $path)
case "Token":
AccessTokenView()
case "User":
Expand Down
16 changes: 8 additions & 8 deletions iOS/swiftui-davinci/Davinci/Davinci/DavinciViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ import PingExternalIdP
/// - Redirect URI
/// - Discovery Endpoint
/// - Other optional fields
public let davinci = DaVinci.createDaVinci { config in
//TODO: Provide here the Server configuration. Add the PingOne server Discovery Endpoint and the OAuth2.0 client details
config.module(OidcModule.config) { oidcValue in
oidcValue.clientId = <#"Client ID"#>
oidcValue.scopes = [<#"scope1"#>, <#"scope2"#>, <#"scope3"#>]
oidcValue.redirectUri = <#"Redirect URI"#>
oidcValue.discoveryEndpoint = <#"Discovery Endpoint"#>
public let daVinci = DaVinci.createDaVinci { config in
config.module(PingDavinci.OidcModule.config) { oidcValue in
oidcValue.clientId = "<#CLIENT_ID#>"
oidcValue.scopes = Set(["openid", "email", "address", "phone", "profile"])
oidcValue.redirectUri = "<#REDIRECT_URI#>"
oidcValue.acrValues = "<#ACR_VALUES#>"
oidcValue.discoveryEndpoint = "<#DISCOVERY_ENDPOINT#>"
}
}

Expand Down Expand Up @@ -60,7 +60,7 @@ class DavinciViewModel: ObservableObject {
}

// Starts the DaVinci orchestration process and retrieves the first node.
let next = await davinci.start()
let next = await daVinci.start()

await MainActor.run {
self.state = DavinciState(previous: next , node: next)
Expand Down
5 changes: 3 additions & 2 deletions iOS/swiftui-davinci/Davinci/Davinci/LogOutView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ struct LogOutView: View {
NextButton(title: "Proceed to logout") {
Task {
await logoutViewModel.logout()
path.removeLast()
path.append("DaVinci")
if path.count > 0 {
path.removeLast()
}
}
}
.navigationTitle("Logout")
Expand Down
25 changes: 20 additions & 5 deletions iOS/swiftui-davinci/Davinci/Davinci/LogOutViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,30 @@ class LogOutViewModel: ObservableObject {
/// - Updated with a completion message when logout is successful.
@Published var logout: String = ""

/// Performs the user logout process using the DaVinci SDK.
/// - Executes the `logout()` method from the DaVinci user object asynchronously.
/// Performs the user logout process using either the DaVinci SDK or OIDC Web SDK.
/// - Executes the appropriate `logout()` method based on which user is authenticated.
/// - Updates the `logout` property with a completion message upon success.
func logout() async {
/// Call the DaVinci SDK's logout method
await davinci.user()?.logout()
var logoutMessage = ""

/// Check for DaVinci user first
if let daVinciUser = await daVinci.daVinciUser() {
await daVinciUser.logout()
logoutMessage = "DaVinci user logout completed"
}
/// Check for OIDC user
if let oidcUser = await oidcLogin.oidcLoginUser() {
await oidcUser.logout()
logoutMessage = "OIDC user logout completed"
}
/// If no authenticated user found
else {
logoutMessage = "No authenticated user found"
}

/// Update the UI on the main thread
await MainActor.run {
logout = "Logout completed"
logout = logoutMessage
}
}
}
82 changes: 82 additions & 0 deletions iOS/swiftui-davinci/Davinci/Davinci/OidcLoginView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//
// OidcLoginView.swift
// Davinci
//
// Copyright (c) 2024 - 2025 Ping Identity Corporation. All rights reserved.
//
// This software may be modified and distributed under the terms
// of the MIT license. See the LICENSE file for details.
//

import Foundation
import SwiftUI
import PingOidc

/// The main view for OIDC login authentication.
struct OidcLoginView: View {
/// The view model that manages the OIDC login flow logic.
@StateObject private var oidcLoginViewModel = OidcLoginViewModel()
/// A binding to the navigation stack path.
@Binding var path: [String]

var body: some View {
ZStack {
ScrollView {
VStack {
Spacer()
// Handle different types of authentication states.
switch oidcLoginViewModel.state {
case .success( _ ):
VStack{}.onAppear {
path.removeLast()
path.append("Token")
}
case .failure(let error):
ErrorView(message: error.localizedDescription)
case .none:
VStack(spacing: 20) {
/// App logo displayed at the top of the view
Image("Logo").resizable().scaledToFill().frame(width: 100, height: 100)

Text("OIDC Login")
.font(.title)
.fontWeight(.bold)

Text("Authenticate using OIDC Web flow")
.font(.subheadline)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)

Button("Start OIDC Login") {
Task {
await oidcLoginViewModel.startOidcLogin()
}
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
.padding()
}
}
}

Spacer()

// Show an activity indicator when loading.
if oidcLoginViewModel.isLoading {
/// Semi-transparent overlay to indicate loading state
Color.black.opacity(0.4)
.edgesIgnoringSafeArea(.all)

/// Circular progress indicator that provides visual feedback during loading operations
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
.scaleEffect(2)
.tint(.blue)
}
}
.navigationTitle("OIDC Login")
}
}
Loading