Skip to content
Merged
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
1 change: 1 addition & 0 deletions RIADigiDoc.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@
Util/Signature/SignatureUtilProtocol.swift,
Util/Theme/ThemeSettings.swift,
Util/Theme/ThemeSettingsProtocol.swift,
Util/WebEid/WebEidUriUtil.swift,
ViewModel/AdvancedSettingsViewModel.swift,
ViewModel/CertificateDetailViewModel.swift,
ViewModel/Crypto/DecryptRootViewModel.swift,
Expand Down
4 changes: 4 additions & 0 deletions RIADigiDoc/RIADigiDoc.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
<!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.developer.associated-domains</key>
<array>
<string>applinks:riadigidoc.ee</string>
</array>
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>TAG</string>
Expand Down
2 changes: 1 addition & 1 deletion RIADigiDoc/UI/Component/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ struct HomeView: View {
}

private func handleIncoming(url: URL) {
let webEidURL = (url.scheme == "web-eid-mobile") ? url : nil
let webEidURL = (WebEidUriUtil.isWebEidUri(url)) ? url : nil

let externalFileURLs: [URL] = (webEidURL != nil) ? [] : getExternalFileURLs(from: url)
handleFiles(externalFileURLs)
Expand Down
100 changes: 53 additions & 47 deletions RIADigiDoc/UI/Component/WebEid/WebEidView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,52 @@ struct WebEidView: View {
)
}

private var alertTitle: String {
languageSettings.localized(
viewModel.alertMessageKey ?? "",
viewModel.alertMessageExtraArguments
)
}

private var alertInfoURL: URL? {
guard
let messageUrl = viewModel.alertMessageUrl,
!messageUrl.isEmpty
else {
return nil
}

let localizedUrl = languageSettings.localized(messageUrl)
guard let url = URL(string: localizedUrl), UIApplication.shared.canOpenURL(url) else {
return nil
}

return url
}

private func handleWebEidOperation(for url: URL) {
let operation = WebEidUriUtil.getOperation(from: url)
switch operation {
case .auth:
viewModel.handleAuth(url: url)
case .cert:
viewModel.handleCertificate(url: url)
case .sign:
viewModel.handleSign(url: url)
case .unknown:
viewModel.handleUnknown(url: url)
}
}

private func activateWebEidSession() {
Task {
if await viewModel.isWebEidSessionActive() {
await nfcViewModel.clearTempCAN()
}
await viewModel.setWebEidSessionActive(true)
}
}

init(
webEidUrl: URL,
) {
Expand Down Expand Up @@ -119,40 +165,22 @@ struct WebEidView: View {
}
}
.alert(
languageSettings.localized(
viewModel.alertMessageKey ?? "",
viewModel.alertMessageExtraArguments
),
alertTitle,
isPresented: $viewModel.showAlertMessage
) {
Button(languageSettings.localized("OK")) {
viewModel.resetErrors()
}

if let messageUrl = viewModel.alertMessageUrl, !messageUrl.isEmpty {
if let alertInfoURL {
Button(languageSettings.localized("Additional information")) {
if let url = URL(string: languageSettings.localized(messageUrl)),
UIApplication.shared.canOpenURL(url) {
openURL(url)
}
openURL(alertInfoURL)
viewModel.resetErrors()
}
}
}
.onAppear {
if let host = webEidUrl.host {

switch host {
case "auth":
viewModel.handleAuth(url: webEidUrl)
case "cert":
viewModel.handleCertificate(url: webEidUrl)
case "sign":
viewModel.handleSign(url: webEidUrl)
default:
viewModel.handleUnknown(url: webEidUrl)
}
}
handleWebEidOperation(for: webEidUrl)
}
.onChange(of: viewModel.relyingPartyResponseEvents) { _, responseURL in
guard let responseURL else { return }
Expand All @@ -166,35 +194,13 @@ struct WebEidView: View {
Toast.show(errorMessage)
}
.onChange(of: webEidUrl) {_, url in
if let host = url.host {

switch host {
case "auth":
viewModel.handleAuth(url: url)
case "cert":
viewModel.handleCertificate(url: url)
case "sign":
viewModel.handleSign(url: url)
default:
viewModel.handleUnknown(url: url)
}
}
handleWebEidOperation(for: url)
}
.onChange(of: viewModel.authRequest) {_, _ in
Task {
if await viewModel.isWebEidSessionActive() {
await nfcViewModel.clearTempCAN()
}
await viewModel.setWebEidSessionActive(true)
}
activateWebEidSession()
}
.onChange(of: viewModel.certRequest) {_, _ in
Task {
if await viewModel.isWebEidSessionActive() {
await nfcViewModel.clearTempCAN()
}
await viewModel.setWebEidSessionActive(true)
}
activateWebEidSession()
}
}
}
Expand Down
62 changes: 62 additions & 0 deletions RIADigiDoc/Util/WebEid/WebEidUriUtil.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2017 - 2026 Riigi Infosüsteemi Amet
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/

import Foundation

public enum WebEidOperation: String, CaseIterable, Sendable {
case auth
case cert
case sign
case unknown

public static func fromOperation(_ operation: String) -> WebEidOperation {
Self.allCases.first { $0.rawValue == operation } ?? WebEidOperation.unknown
}
}

public enum WebEidUriUtil {
private static let customScheme = "web-eid-mobile"
private static let appLinksHost = "riadigidoc.ee"

public static func isWebEidUri(_ url: URL) -> Bool {
getOperation(from: url) != WebEidOperation.unknown
}

public static func getOperation(from url: URL) -> WebEidOperation {
var operation: String?

#if DEBUG
let isCustomSchemeMatch = url.scheme == customScheme
#else
let isCustomSchemeMatch = false
#endif

if isCustomSchemeMatch {
operation = url.host
} else if url.scheme == "https", url.host == appLinksHost {
operation = url.pathComponents.dropFirst().first
} else {
operation = WebEidOperation.unknown.rawValue
}


guard let operation else { return WebEidOperation.unknown }
return WebEidOperation.fromOperation(operation)
}
}
129 changes: 129 additions & 0 deletions RIADigiDocTests/Util/WebEid/WebEidUriUtilTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright 2017 - 2026 Riigi Infosüsteemi Amet
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/


import Foundation
import Testing

struct WebEidUriUtilTests {

private func makeURL(_ string: String) -> URL {
URL(string: string)!
}

@Test
func isWebEidUri_appLinks_auth() {
#expect(WebEidUriUtil.isWebEidUri(makeURL("https://riadigidoc.ee/auth")))
}

@Test
func isWebEidUri_appLinks_cert() {
#expect(WebEidUriUtil.isWebEidUri(makeURL("https://riadigidoc.ee/cert")))
}

@Test
func isWebEidUri_appLinks_sign() {
#expect(WebEidUriUtil.isWebEidUri(makeURL("https://riadigidoc.ee/sign")))
}

@Test
func isWebEidUri_appLinks_unknownOperation() {
#expect(!WebEidUriUtil.isWebEidUri(makeURL("https://riadigidoc.ee/unknown")))
}

@Test
func isWebEidUri_wrongHost() {
#expect(!WebEidUriUtil.isWebEidUri(makeURL("https://evil.com/auth")))
}

@Test
func isWebEidUri_contentScheme() {
#expect(!WebEidUriUtil.isWebEidUri(makeURL("content://some/path")))
}

@Test
func isWebEidUri_fileScheme() {
#expect(!WebEidUriUtil.isWebEidUri(makeURL("file:///some/path")))
}

@Test
func getOperation_appLinks_auth() {
#expect(WebEidUriUtil.getOperation(from: makeURL("https://riadigidoc.ee/auth#dGVzdA")) == .auth)
}

@Test
func getOperation_appLinks_cert() {
#expect(WebEidUriUtil.getOperation(from: makeURL("https://riadigidoc.ee/cert#dGVzdA")) == .cert)
}

@Test
func getOperation_appLinks_sign() {
#expect(WebEidUriUtil.getOperation(from: makeURL("https://riadigidoc.ee/sign#dGVzdA")) == .sign)
}

@Test
func getOperation_appLinks_unknownOperation_returnsNil() {
#expect(WebEidUriUtil.getOperation(from: makeURL("https://riadigidoc.ee/unknown")) == .unknown)
}

@Test
func getOperation_unrelatedUri_returnsNil() {
#expect(WebEidUriUtil.getOperation(from: makeURL("https://example.com/auth")) == .unknown)
}

@Test
func isWebEidUri_customScheme_auth() {
#expect(WebEidUriUtil.isWebEidUri(makeURL("web-eid-mobile://auth")))
}

@Test
func isWebEidUri_customScheme_cert() {
#expect(WebEidUriUtil.isWebEidUri(makeURL("web-eid-mobile://cert")))
}

@Test
func isWebEidUri_customScheme_sign() {
#expect(WebEidUriUtil.isWebEidUri(makeURL("web-eid-mobile://sign")))
}

@Test
func isWebEidUri_customScheme_unknownOperation() {
#expect(!WebEidUriUtil.isWebEidUri(makeURL("web-eid-mobile://unknown")))
}

@Test
func getOperation_customScheme_auth() {
#expect(WebEidUriUtil.getOperation(from: makeURL("web-eid-mobile://auth#dGVzdA")) == .auth)
}

@Test
func getOperation_customScheme_cert() {
#expect(WebEidUriUtil.getOperation(from: makeURL("web-eid-mobile://cert#dGVzdA")) == .cert)
}

@Test
func getOperation_customScheme_sign() {
#expect(WebEidUriUtil.getOperation(from: makeURL("web-eid-mobile://sign#dGVzdA")) == .sign)
}

@Test
func getOperation_unknownOperation_returnsNil() {
#expect(WebEidUriUtil.getOperation(from: makeURL("web-eid-mobile://unknown")) == .unknown)
}
}
Loading