diff --git a/CHANGELOG b/CHANGELOG index c7cbc83f7bc6a8734153ef71c8433fd66fbc4a15..f0edbf6a8dcde3259319c1adae847ffea941fc09 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +# 3.1.1 +### Bugfixes +* Our new debugging framework sentry helped us to find and fix various runtime bugs and crashes (#148, #144, #147, #145, #146) + # 3.1.0 ### Features diff --git a/Network Share Mounter/Base.lproj/Main.storyboard b/Network Share Mounter/Base.lproj/Main.storyboard index e9ec84629da1aaf28b80348efe312e859d97214e..157f835dec22bfb86f0b4b720e00ee142c746f0f 100644 --- a/Network Share Mounter/Base.lproj/Main.storyboard +++ b/Network Share Mounter/Base.lproj/Main.storyboard @@ -1031,7 +1031,7 @@ The connection will be testet immediately and will be added only if the server i </textFieldCell> </textField> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8Vv-en-BKS"> - <rect key="frame" x="327" y="13" width="120" height="32"/> + <rect key="frame" x="290" y="13" width="139" height="32"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <buttonCell key="cell" type="push" title="Button" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Wh9-zC-thW"> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> @@ -1041,12 +1041,23 @@ The connection will be testet immediately and will be added only if the server i <action selector="cancelKlicked:" target="6r4-rb-dJs" id="P3P-mS-ZlD"/> </connections> </button> + <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ujD-hy-6bv"> + <rect key="frame" x="153" y="13" width="139" height="32"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <buttonCell key="cell" type="push" title="Button" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Hsw-le-rfV"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <connections> + <action selector="removeKlicked:" target="6r4-rb-dJs" id="QMx-Jb-are"/> + </connections> + </button> <progressIndicator fixedFrame="YES" maxValue="100" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="tuu-EA-5cz"> - <rect key="frame" x="449" y="24" width="16" height="16"/> + <rect key="frame" x="430" y="22" width="16" height="16"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> </progressIndicator> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="hkh-AE-LKO"> - <rect key="frame" x="466" y="13" width="120" height="32"/> + <rect key="frame" x="447" y="13" width="139" height="32"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <buttonCell key="cell" type="push" title="Button" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Q2i-vC-PLt"> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> @@ -1071,12 +1082,12 @@ The connection will be testet immediately and will be added only if the server i <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="21Z-Um-buj"> <rect key="frame" x="278" y="182" width="305" height="25"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> - <popUpButtonCell key="cell" type="push" title="Item 1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="soC-7J-Msf" id="Du8-Ci-I7g"> + <popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="soC-7J-Msf" id="Du8-Ci-I7g"> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> <font key="font" usesAppearanceFont="YES"/> <menu key="menu" id="K43-nA-yPW"> <items> - <menuItem title="Item 1" state="on" id="soC-7J-Msf"/> + <menuItem state="on" id="soC-7J-Msf"/> <menuItem title="Item 2" id="cB8-YR-KqE"/> <menuItem title="Item 3" id="6wl-yy-c0j"/> </items> @@ -1095,7 +1106,7 @@ express individuality and make a statement. jkdsjhkds </string> </textFieldCell> </textField> <button tag="1" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="OfG-RR-odf"> - <rect key="frame" x="281" y="12" width="20" height="37"/> + <rect key="frame" x="132" y="12" width="20" height="37"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" imagePosition="only" alignment="center" tag="1" imageScaling="proportionallyDown" inset="2" id="rh8-cW-6rn"> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> @@ -1121,6 +1132,7 @@ express individuality and make a statement. jkdsjhkds </string> <outlet property="logo" destination="FOX-uX-n7a" id="vLA-a1-cTo"/> <outlet property="password" destination="3SP-aS-pOn" id="jdf-M4-krx"/> <outlet property="passwordText" destination="BzW-i0-bsL" id="Bhf-WC-xOf"/> + <outlet property="removeButtonText" destination="ujD-hy-6bv" id="gOf-DH-V7Q"/> <outlet property="spinner" destination="tuu-EA-5cz" id="1ny-jb-Pik"/> <outlet property="username" destination="fdl-dJ-Prk" id="Ama-jp-Eai"/> <outlet property="usernameText" destination="wMc-Nf-mq7" id="d1H-jT-KYP"/> @@ -1454,6 +1466,6 @@ The connection will be testet immediately and will be added only if the server i <image name="NSAddTemplate" width="18" height="17"/> <image name="NSRemoveTemplate" width="18" height="5"/> <image name="nsm_logo" width="512" height="512"/> - <image name="questionmark.circle" catalog="system" width="15" height="15"/> + <image name="questionmark.circle" catalog="system" width="20" height="20"/> </resources> </document> diff --git a/Network Share Mounter/Localizable.xcstrings b/Network Share Mounter/Localizable.xcstrings index 879d1f1bf55d129395839f7cad2dfdb8800f153e..6a36ed0b71ebfb3152b4e15eb69d662ff255deb7 100644 --- a/Network Share Mounter/Localizable.xcstrings +++ b/Network Share Mounter/Localizable.xcstrings @@ -265,7 +265,7 @@ } }, "authui-krb-infotext" : { - "comment" : "informative test for kerberos auth window", + "comment" : "informative text for kerberos auth window", "localizations" : { "de" : { "stringUnit" : { @@ -428,6 +428,47 @@ } } }, + "authui-remove-text" : { + "comment" : "text on remove button", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eintrag löschen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delete Entry" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eliminar entrada" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Supprimer l'entrée" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Elimina voce" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verwijder invoer" + } + } + } + }, "authui-username-text" : { "comment" : "value shown as username", "localizations" : { diff --git a/Network Share Mounter/lookups/SRVResolver.swift b/Network Share Mounter/lookups/SRVResolver.swift index 08112a406d0a5d1aa9afe3493e58ff0ba7304ce9..ab2c2b2ebb075205be29f399883d2235dab5e309 100644 --- a/Network Share Mounter/lookups/SRVResolver.swift +++ b/Network Share Mounter/lookups/SRVResolver.swift @@ -11,133 +11,148 @@ import Foundation import dnssd import Combine -// Result needs errors, so here's one - +/// Enum representing possible errors during SRV resolution. public enum SRVResolverError: String, Error, Codable { case unableToComplete = "Unable to complete lookup" } +/// Type alias for the result of an SRV resolution. public typealias SRVResolverResult = Result<SRVResult, SRVResolverError> + +/// Type alias for the completion handler used in SRV resolution. public typealias SRVResolverCompletion = (SRVResolverResult) -> Void +/// Class responsible for resolving SRV records. class SRVResolver { - private let queue = DispatchQueue.init(label: "SRVResolution") - private var dispatchSourceRead: DispatchSourceRead?; - private var timeoutTimer: DispatchSourceTimer?; + private let queue = DispatchQueue(label: "SRVResolution") + private var dispatchSourceRead: DispatchSourceRead? + private var timeoutTimer: DispatchSourceTimer? private var serviceRef: DNSServiceRef? - private var socket: dnssd_sock_t = -1; + private var socket: dnssd_sock_t = -1 private var query: String? - // default to 5 sec lookups, we could maybe make this longer - // but if you take more than 5 secs to look things up, you'll - // probably have other problems - + /// Default timeout for DNS lookups. private let timeout = TimeInterval(5) var results = [SRVRecord]() var completion: SRVResolverCompletion? - // this processes any results from the system DNS resolver - // we could parse all the things, but we don't really need the info... - + /// Callback function for processing DNS results. let queryCallback: DNSServiceQueryRecordReply = { (sdRef, flags, interfaceIndex, errorCode, fullname, rrtype, rrclass, rdlen, rdata, ttl, context) -> Void in guard let context = context else { return } - let request: SRVResolver = SRVResolver.bridge(context) + let resolver: SRVResolver = SRVResolver.bridge(context) if let data = rdata?.assumingMemoryBound(to: UInt8.self), - let record = SRVRecord(data: Data.init(bytes: data, count: Int(rdlen))) { - request.results.append(record) + let record = SRVRecord(data: Data(bytes: data, count: Int(rdlen))) { + resolver.results.append(record) } - if ((flags & kDNSServiceFlagsMoreComing) == 0) { - request.success() + if (flags & kDNSServiceFlagsMoreComing) == 0 { + resolver.success() } } - // These allow for the ObjC -> Swift conversion of a pointer - // The DNS APIs are a bit... unique - - static func bridge<T:AnyObject>(_ obj : T) -> UnsafeMutableRawPointer { - return Unmanaged.passUnretained(obj).toOpaque(); + /// Bridges an Objective-C object to a Swift pointer. + static func bridge<T: AnyObject>(_ obj: T) -> UnsafeMutableRawPointer { + return Unmanaged.passUnretained(obj).toOpaque() } - static func bridge<T:AnyObject>(_ ptr : UnsafeMutableRawPointer) -> T { - return Unmanaged<T>.fromOpaque(ptr).takeUnretainedValue(); + /// Bridges a Swift pointer back to an Objective-C object. + static func bridge<T: AnyObject>(_ ptr: UnsafeMutableRawPointer) -> T { + return Unmanaged<T>.fromOpaque(ptr).takeUnretainedValue() } + /// Handles a failed SRV resolution. func fail() { stopQuery() - completion?(SRVResolverResult.failure(.unableToComplete)) + completion?(.failure(.unableToComplete)) } + /// Handles a successful SRV resolution. func success() { stopQuery() let result = SRVResult(SRVRecords: results, query: query ?? "Unknown Query") - completion?(SRVResolverResult.success(result)) + completion?(.success(result)) } + /// Stops the DNS query and cleans up resources. private func stopQuery() { - - // be nice and clean things up - self.timeoutTimer?.cancel() - self.dispatchSourceRead?.cancel() + timeoutTimer?.cancel() + dispatchSourceRead?.cancel() + if let serviceRef = serviceRef { + DNSServiceRefDeallocate(serviceRef) + self.serviceRef = nil + } } - func resolve(query: String, completion: SRVResolverCompletion? = nil) { - - if let completion = completion { - self.completion = completion - } - + /// Initiates the SRV resolution process. + /// - Parameters: + /// - query: The DNS query string. + /// - completion: The completion handler to call when the resolution is complete. + func resolve(query: String, completion: @escaping SRVResolverCompletion) { + self.completion = completion self.query = query - let namec = query.cString(using: .utf8) + guard let namec = query.cString(using: .utf8) else { + fail() + return + } - let result = DNSServiceQueryRecord(&self.serviceRef, kDNSServiceFlagsReturnIntermediates, UInt32(0), namec, UInt16(kDNSServiceType_SRV), UInt16(kDNSServiceClass_IN), queryCallback, SRVResolver.bridge(self)) + let result = DNSServiceQueryRecord( + &serviceRef, + kDNSServiceFlagsReturnIntermediates, + 0, // query on all interfaces. + namec, + UInt16(kDNSServiceType_SRV), + UInt16(kDNSServiceClass_IN), + queryCallback, + SRVResolver.bridge(self) + ) switch result { case DNSServiceErrorType(kDNSServiceErr_NoError): - - guard let sdRef = self.serviceRef else { + guard let sdRef = serviceRef else { fail() return } - self.socket = DNSServiceRefSockFD(self.serviceRef) + socket = DNSServiceRefSockFD(sdRef) - guard self.socket != -1 else { + guard socket != -1 else { fail() return } - self.dispatchSourceRead = DispatchSource.makeReadSource(fileDescriptor: self.socket, queue: self.queue) + dispatchSourceRead = DispatchSource.makeReadSource(fileDescriptor: socket, queue: queue) - self.dispatchSourceRead?.setEventHandler(handler: { + dispatchSourceRead?.setEventHandler(handler: { let res = DNSServiceProcessResult(sdRef) if res != kDNSServiceErr_NoError { self.fail() } }) - self.dispatchSourceRead?.setCancelHandler(handler: { - DNSServiceRefDeallocate(self.serviceRef) + dispatchSourceRead?.setCancelHandler(handler: { + if let serviceRef = self.serviceRef { + DNSServiceRefDeallocate(serviceRef) + } }) - self.dispatchSourceRead?.resume() + dispatchSourceRead?.resume() - self.timeoutTimer = DispatchSource.makeTimerSource(flags: [], queue: self.queue) + timeoutTimer = DispatchSource.makeTimerSource(flags: [], queue: queue) - self.timeoutTimer?.setEventHandler(handler: { + timeoutTimer?.setEventHandler(handler: { self.fail() }) - let deadline = DispatchTime(uptimeNanoseconds: DispatchTime.now().uptimeNanoseconds + UInt64(timeout * Double(NSEC_PER_SEC))) - self.timeoutTimer?.schedule(deadline: deadline, repeating: .infinity, leeway: DispatchTimeInterval.never) - self.timeoutTimer?.resume() + let deadline = DispatchTime.now() + timeout + timeoutTimer?.schedule(deadline: deadline, repeating: .infinity, leeway: .never) + timeoutTimer?.resume() default: - self.fail() + fail() } } } diff --git a/Network Share Mounter/managers/AccountsManager.swift b/Network Share Mounter/managers/AccountsManager.swift index 5a47b83c3be2b7ea143055c9f8416a439709a72e..53b76922b0e730dd872a55e6e0b1a8ffa111bd28 100644 --- a/Network Share Mounter/managers/AccountsManager.swift +++ b/Network Share Mounter/managers/AccountsManager.swift @@ -9,43 +9,52 @@ import Foundation -protocol AccountUpdate { +/// Protocol defining the methods for updating account information. +protocol AccountUpdate: AnyObject { func updateAccounts(accounts: [DogeAccount]) } +/// Actor responsible for managing user accounts. actor AccountsManager { var prefs = PreferenceManager() var accounts = [DogeAccount]() var delegates = [AccountUpdate]() + /// Singleton instance of AccountsManager. static let shared = AccountsManager() private var isInitialized = false + /// Private initializer to enforce singleton pattern. private init() {} + /// Initializes the AccountsManager, performing necessary setup tasks. func initialize() async { guard !isInitialized else { return } - // perform some FAU tasks - if prefs.string(for: .kerberosRealm)?.lowercased() == FAU.kerberosRealm.lowercased() { - if !prefs.bool(for: .keyChainPrefixManagerMigration) { + // Perform FAU-specific tasks if the Kerberos realm matches. + if await prefs.string(for: .kerberosRealm)?.lowercased() == FAU.kerberosRealm.lowercased() { + if await !prefs.bool(for: .keyChainPrefixManagerMigration) { let migrator = Migrator() await migrator.migrate() } } - loadAccounts() + + // Load accounts from persistent storage. + await loadAccounts() isInitialized = true } - private func loadAccounts() { - let decoder = PropertyListDecoder.init() - if let accountsData = prefs.data(for: .accounts), + /// Loads accounts from UserDefaults and updates the internal accounts list. + private func loadAccounts() async { + let decoder = PropertyListDecoder() + if let accountsData = await prefs.data(for: .accounts), let accountsList = try? decoder.decode(DogeAccounts.self, from: accountsData) { var uniqueAccounts = [DogeAccount]() var processedUPNs = Set<String>() + // Filter and store unique accounts based on UPN. for account in accountsList.accounts { let lowercasedUPN = account.upn.lowercased() if !lowercasedUPN.starts(with: "@") && !processedUPNs.contains(lowercasedUPN) { @@ -55,45 +64,51 @@ actor AccountsManager { } accounts = uniqueAccounts } + + // Notify delegates about the updated accounts list. updateDelegates() } - func saveAccounts() { - let encoder = PropertyListEncoder.init() - if let accountData = try? encoder.encode(DogeAccounts.init(accounts: accounts)) { - prefs.set(for: .accounts, value: accountData) - prefs.defaults.setValue(accountData, forKey: PreferenceKeys.accounts.rawValue) + /// Saves the current accounts list to UserDefaults. + func saveAccounts() async { + let encoder = PropertyListEncoder() + if let accountData = try? encoder.encode(DogeAccounts(accounts: accounts)) { + await prefs.set(for: .accounts, value: accountData) } + + // Notify delegates about the updated accounts list. updateDelegates() } - func addAccount(account: DogeAccount) { + /// Adds a new account and updates the persistent storage. + func addAccount(account: DogeAccount) async { accounts.append(account) - saveAccounts() + await saveAccounts() } - func deleteAccount(account: DogeAccount) { - accounts.removeAll() { $0 == account } - saveAccounts() + /// Deletes an existing account and updates the persistent storage. + func deleteAccount(account: DogeAccount) async { + accounts.removeAll { $0 == account } + await saveAccounts() } + /// Retrieves an account for a given principal. func accountForPrincipal(principal: String) -> DogeAccount? { - for account in accounts { if account.upn.lowercased() == principal.lowercased() { return account } } - return nil } + /// Returns a list of all unique domains from the accounts. func returnAllDomains() -> [String] { var domains = [String]() for account in accounts { if let userDomain = account.upn.userDomain(), - !domains.contains(userDomain){ + !domains.contains(userDomain) { domains.append(userDomain) } } @@ -101,6 +116,7 @@ actor AccountsManager { return domains } + /// Notifies all registered delegates about account updates. private func updateDelegates() { for delegate in delegates { delegate.updateAccounts(accounts: accounts) @@ -109,6 +125,7 @@ actor AccountsManager { } extension AccountsManager { + /// Adds a delegate to receive account updates. func addDelegate(delegate: AccountUpdate) { delegates.append(delegate) } diff --git a/Network Share Mounter/mul.lproj/Main.xcstrings b/Network Share Mounter/mul.lproj/Main.xcstrings index 8597decfde1efbc15908ddc18ba93a5644f73319..2e849bb8dce9b6454b800bb16d611333615b8cfd 100644 --- a/Network Share Mounter/mul.lproj/Main.xcstrings +++ b/Network Share Mounter/mul.lproj/Main.xcstrings @@ -2921,6 +2921,18 @@ } } }, + "Hsw-le-rfV.title" : { + "comment" : "Class = \"NSButtonCell\"; title = \"Button\"; ObjectID = \"Hsw-le-rfV\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Button" + } + } + } + }, "HyV-fh-RgO.title" : { "comment" : "Class = \"NSMenu\"; title = \"View\"; ObjectID = \"HyV-fh-RgO\";", "extractionState" : "extracted_with_value", @@ -4978,18 +4990,6 @@ } } }, - "soC-7J-Msf.title" : { - "comment" : "Class = \"NSMenuItem\"; title = \"Item 1\"; ObjectID = \"soC-7J-Msf\";", - "extractionState" : "extracted_with_value", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Item 1" - } - } - } - }, "SyX-Mr-uDg.title" : { "comment" : "Class = \"NSTextFieldCell\"; title = \"You can mount any Windows (smb/cifs) network share as long as you know the server name and the export path. \\nEnter the share as smb://server.name/export/path\\nThe connection will be testet immediately and will be added only if the server is reachable and the share is available.\"; ObjectID = \"SyX-Mr-uDg\";", "extractionState" : "extracted_with_value", diff --git a/Network Share Mounter/preferences/PreferenceManager.swift b/Network Share Mounter/preferences/PreferenceManager.swift index 072a7975775c15af48325514bc6373684d68123c..d1e565e69f788e7fe791ebab8a720695a469ed07 100644 --- a/Network Share Mounter/preferences/PreferenceManager.swift +++ b/Network Share Mounter/preferences/PreferenceManager.swift @@ -74,7 +74,7 @@ struct PreferenceManager { defaults.set(user.userPrincipal.lowercased(), forKey: PreferenceKeys.lastUser.rawValue) if let passwordAging = user.passwordAging, passwordAging { - if let expireDate = user.computedExireDate { + if let expireDate = user.computedExpireDate { self.set(for: .userPasswordExpireDate, value: expireDate) } } else { @@ -85,7 +85,7 @@ struct PreferenceManager { stateDefaults.set(user.cn, forKey: PreferenceKeys.userCN.rawValue) stateDefaults.set(user.groups, forKey: PreferenceKeys.userGroups.rawValue) - stateDefaults.set(user.computedExireDate, forKey: PreferenceKeys.userPasswordExpireDate.rawValue) + stateDefaults.set(user.computedExpireDate, forKey: PreferenceKeys.userPasswordExpireDate.rawValue) stateDefaults.set(user.passwordSet, forKey: PreferenceKeys.userPasswordSetDate.rawValue) stateDefaults.set(user.homeDirectory, forKey: PreferenceKeys.userHome.rawValue) stateDefaults.set(user.userPrincipal, forKey: PreferenceKeys.userPrincipal.rawValue) @@ -98,24 +98,28 @@ struct PreferenceManager { stateDefaults.set(user.lastName, forKey: PreferenceKeys.userLastName.rawValue) stateDefaults.set(Date(), forKey: PreferenceKeys.userLastChecked.rawValue) - var allUsers = stateDefaults.dictionary(forKey: PreferenceKeys.allUserInformation.rawValue) ?? [String: [String: AnyObject]]() - allUsers[user.userPrincipal] = [ - "CN": user.cn, - "groups": user.groups, - "UserPasswordExpireDate": user.computedExireDate?.description ?? "", - "UserHome": user.homeDirectory ?? "", - "UserPrincipal": user.userPrincipal, - "CustomLDAPAttributesResults": user.customAttributes?.description ?? "", - "UserShortName": user.shortName, - "UserUPN": user.upn, - "UserEmail": user.email ?? "", - "UserFullName": user.fullName, - "UserFirstName": user.firstName, - "UserLastName": user.lastName, - "UserLastChecked": Date() - ] + // Break down the complex expression into simpler steps + var allUsers = stateDefaults.dictionary(forKey: PreferenceKeys.allUserInformation.rawValue) as? [String: [String: AnyObject]] ?? [:] + + var userInfo = [String: AnyObject]() + userInfo["CN"] = user.cn as AnyObject + userInfo["groups"] = user.groups as AnyObject + userInfo["UserPasswordExpireDate"] = user.computedExpireDate?.description as AnyObject? ?? "" as AnyObject + userInfo["UserHome"] = user.homeDirectory as AnyObject? ?? "" as AnyObject + userInfo["UserPrincipal"] = user.userPrincipal as AnyObject + userInfo["CustomLDAPAttributesResults"] = user.customAttributes?.description as AnyObject? ?? "" as AnyObject + userInfo["UserShortName"] = user.shortName as AnyObject + userInfo["UserUPN"] = user.upn as AnyObject + userInfo["UserEmail"] = user.email as AnyObject? ?? "" as AnyObject + userInfo["UserFullName"] = user.fullName as AnyObject + userInfo["UserFirstName"] = user.firstName as AnyObject + userInfo["UserLastName"] = user.lastName as AnyObject + userInfo["UserLastChecked"] = Date() as AnyObject + + allUsers[user.userPrincipal] = userInfo stateDefaults.setValue(allUsers, forKey: PreferenceKeys.allUserInformation.rawValue) } + private func readPropertyList() -> [String: Any]? { guard let plistPath = Bundle.main.path(forResource: "DefaultValues", ofType: "plist"), diff --git a/Network Share Mounter/view/KrbAuthViewController.swift b/Network Share Mounter/view/KrbAuthViewController.swift index 14f9deca2fd1bfb71f3f837a6e4a8d959d92cc2e..1aa4c156c859e656a27e1a069b5c1afa90aa586f 100644 --- a/Network Share Mounter/view/KrbAuthViewController.swift +++ b/Network Share Mounter/view/KrbAuthViewController.swift @@ -11,90 +11,178 @@ import Cocoa import OSLog import dogeADAuth -class KrbAuthViewController: NSViewController, AccountUpdate { +class KrbAuthViewController: NSViewController, AccountUpdate, NSTextFieldDelegate { func updateAccounts(accounts: [DogeAccount]) { Task { @MainActor in await buildAccountsMenu() } } - // MARK: - help messages - var helpText = [NSLocalizedString("Sorry, no help available", comment: "this should not happen"), - NSLocalizedString("authui-infotext", comment: "")] + // MARK: - Properties var session: dogeADSession? var prefs = PreferenceManager() - let accountsManager = AccountsManager.shared + // UI Outlets @IBOutlet weak var logo: NSImageView! @IBOutlet weak var usernameText: NSTextField! @IBOutlet weak var passwordText: NSTextField! @IBOutlet weak var username: NSTextField! @IBOutlet weak var password: NSSecureTextField! @IBOutlet weak var authenticateButtonText: NSButton! + @IBOutlet weak var removeButtonText: NSButton! @IBOutlet weak var cancelButtonText: NSButton! @IBOutlet weak var spinner: NSProgressIndicator! @IBOutlet weak var accountsList: NSPopUpButton! @IBOutlet weak var krbAuthViewTitle: NSTextField! @IBOutlet weak var krbAuthViewInfoText: NSTextField! - @IBAction func authenticateKlicked(_ sender: Any) { - startOperations() + // Help messages + var helpText = [ + NSLocalizedString("Sorry, no help available", comment: "this should not happen"), + NSLocalizedString("authui-infotext", comment: "") + ] + + // MARK: - View Lifecycle + override func viewDidLoad() { + super.viewDidLoad() Task { - session = dogeADSession(domain: username.stringValue.userDomain() ?? prefs.string(for: .kerberosRealm) ?? "", user: username.stringValue) - session?.setupSessionFromPrefs(prefs: prefs) - session?.userPass = password.stringValue - session?.delegate = self - await session?.authenticate(authTestOnly: false) + await setupView() + } + + // Add observer for username field changes + username.target = self + username.action = #selector(usernameDidChange) + + // Add observer for password field changes + password.target = self + password.action = #selector(passwordDidChange) + + // Set the delegate for the password field + password.delegate = self + } + + // MARK: - Setup Methods + private func setupView() async { + krbAuthViewTitle.stringValue = NSLocalizedString("authui-krb-title", comment: "title of kerberos auth window") + krbAuthViewInfoText.stringValue = NSLocalizedString("authui-krb-infotext", comment: "informative text for kerberos auth window") + + // Configure UI based on environment + configureUIForEnvironment() + + authenticateButtonText.title = NSLocalizedString("authui-button-text", comment: "text on authenticate button") + removeButtonText.title = NSLocalizedString("authui-remove-text", comment: "text on remove button") + cancelButtonText.title = NSLocalizedString("cancel", comment: "cancel") + spinner.isHidden = true + + await buildAccountsMenu() + accountsList.action = #selector(popUpChange) + await accountsManager.addDelegate(delegate: self) + } + + private func configureUIForEnvironment() { + if prefs.string(for: .kerberosRealm)?.lowercased() == FAU.kerberosRealm.lowercased() { + usernameText.stringValue = NSLocalizedString("authui-username-text-FAU", comment: "value shown as FAU username") + passwordText.stringValue = NSLocalizedString("authui-password-text-FAU", comment: "value shown as FAU password") + logo.image = NSImage(named: FAU.authenticationDialogImage) + } else { + usernameText.stringValue = NSLocalizedString("authui-username-text", comment: "value shown as username") + passwordText.stringValue = NSLocalizedString("authui-password-text", comment: "value shown as password") + logo.image = NSImage(named: prefs.string(for: .authenticationDialogImage)!) } } + // MARK: - Actions + @IBAction func authenticateKlicked(_ sender: Any) { + authenticateUser(userPassword: self.password.stringValue) + } + @IBAction func cancelKlicked(_ sender: Any) { dismiss(nil) } - // MARK: - initialize view - override func viewDidLoad() { - super.viewDidLoad() + @IBAction func helpButtonClicked(_ sender: NSButton) { + showHelpPopover(for: sender) + } + + @IBAction func removeKlicked(_ sender: Any) { Task { - krbAuthViewTitle.stringValue = NSLocalizedString("authui-krb-title", comment: "title of kerberos auth window") - krbAuthViewInfoText.stringValue = NSLocalizedString("authui-krb-infotext", comment: "informative test for kerberos auth window") - // if NSM is used in FAU environment use corporate images and labels - if prefs.string(for: .kerberosRealm)?.lowercased() == FAU.kerberosRealm.lowercased() { - usernameText.stringValue = NSLocalizedString("authui-username-text-FAU", comment: "value shown as FAU username") - passwordText.stringValue = NSLocalizedString("authui-password-text-FAU", comment: "value shown as FAU password") - logo.image = NSImage(named: FAU.authenticationDialogImage) - } else { - usernameText.stringValue = NSLocalizedString("authui-username-text", comment: "value shown as username") - passwordText.stringValue = NSLocalizedString("authui-password-text", comment: "value shown as password") - // force unwrap is ok since authenticationDialogImage is a registered default in AppDelegate - logo.image = NSImage(named: prefs.string(for: .authenticationDialogImage)!) + // Ensure the operation is performed on the main thread + await MainActor.run { + // Get the selected item title + guard let selectedTitle = accountsList.selectedItem?.title else { + Logger.KrbAuthViewController.debug("No account selected for removal") + return + } + + // Remove the indicator if present + let accountTitle = selectedTitle.replacingOccurrences(of: " ◀︎", with: "") + + // Find the corresponding DogeAccount + Task { + let accounts = await accountsManager.accounts + if let accountToRemove = accounts.first(where: { $0.displayName == accountTitle }) { + await accountsManager.deleteAccount(account: accountToRemove) + Logger.KrbAuthViewController.debug("Account removed: \(accountToRemove.displayName)") + + // Rebuild the accounts menu to reflect the changes + await buildAccountsMenu() + } else { + Logger.KrbAuthViewController.debug("Account not found for removal: \(accountTitle)") + } + } } - authenticateButtonText.title = NSLocalizedString("authui-button-text", comment: "text on authenticate button") - cancelButtonText.title = NSLocalizedString("cancel", comment: "cancel") - self.spinner.isHidden = true + } + } + + // MARK: - Authentication + private func authenticateUser(userPassword: String) { + Task { + // Set up the session with the provided username and domain + session = dogeADSession(domain: username.stringValue.userDomain() ?? prefs.string(for: .kerberosRealm) ?? "", user: username.stringValue) - await buildAccountsMenu() - accountsList.action = #selector(popUpChange) - await accountsManager.addDelegate(delegate: self) + // Configure the session with preferences + session?.setupSessionFromPrefs(prefs: prefs) + + // Set the user password + session?.userPass = userPassword + + // Assign the delegate to self to handle authentication callbacks + session?.delegate = self + + // Start the authentication process + await session?.authenticate(authTestOnly: false) } } - @IBAction func helpButtonClicked(_ sender: NSButton) { - // swiftlint:disable force_cast - let helpPopoverViewController = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("HelpPopoverViewController")) as! HelpPopoverViewController - // swiftlint:enable force_cast - let popover = NSPopover() - popover.contentViewController = helpPopoverViewController - helpPopoverViewController.helpText = helpText[sender.tag] - popover.animates = true - popover.show(relativeTo: sender.frame, of: self.view, preferredEdge: .minY) - popover.behavior = .transient + private func handleSuccessfulAuthentication() async { + if let principal = session?.userPrincipal { + if let account = await accountsManager.accountForPrincipal(principal: principal) { + let pwm = KeychainManager() + do { + try pwm.saveCredential(forUsername: account.upn.lowercased(), andPassword: password.stringValue) + Logger.authUI.debug("Password successfully updated in keychain") + } catch { + Logger.authUI.debug("Failed saving password in keychain") + } + } else { + let newAccount = DogeAccount(displayName: principal, upn: principal, hasKeychainEntry: prefs.bool(for: .useKeychain)) + let pwm = KeychainManager() + do { + try pwm.saveCredential(forUsername: principal.lowercased(), andPassword: password.stringValue) + Logger.authUI.debug("Account successfully added to keychain") + } catch { + Logger.authUI.debug("Error adding account to Keychain") + } + await accountsManager.addAccount(account: newAccount) + } + } + NotificationCenter.default.post(name: .nsmNotification, object: nil, userInfo: ["krbAuthenticated": MounterError.krbAuthSuccessful]) + closeWindow() } - /// start some UI operations like spinner animation, buttons are not clickable - /// text fields are not editable and so on + // MARK: - UI Operations fileprivate func startOperations() { Task { @MainActor in spinner.isHidden = false @@ -106,8 +194,6 @@ class KrbAuthViewController: NSViewController, AccountUpdate { } } - /// stop some UI operations like spinner animation, buttons are clickable - /// text fields are editable and so on fileprivate func stopOperations() { Task { @MainActor in spinner.isHidden = true @@ -122,34 +208,30 @@ class KrbAuthViewController: NSViewController, AccountUpdate { private func showAlert(message: String) { Task { @MainActor in let alert = NSAlert() - - var text = message - - if message.contains("unable to reach any KDC in realm") { - text = "Unable to reach any Kerberos servers in this domain. Please check your network connection and try again." - } else if message.contains("Client") && text.contains("unknown") { - text = "Your username could not be found. Please check the spelling and try again." - } else if message.contains("RSA private encrypt failed") { - text = "Your PIN is incorrect" - } - switch message { - case "Preauthentication failed": - text = "Incorrect username or password." - case "Password has expired": - text = "Password has expired." - default: - break - } - alert.messageText = text - if alert.runModal() == .alertFirstButtonReturn { - Logger.authUI.debug("showing alert") - } + alert.messageText = formatAlertMessage(message) + alert.runModal() + Logger.authUI.debug("Showing alert with message: \(message)") } } - func windowWillClose(_ notification: Notification) { - stopOperations() - session = nil + private func formatAlertMessage(_ message: String) -> String { + var text = message + if message.contains("unable to reach any KDC in realm") { + text = "Unable to reach any Kerberos servers in this domain. Please check your network connection and try again." + } else if message.contains("Client") && text.contains("unknown") { + text = "Your username could not be found. Please check the spelling and try again." + } else if message.contains("RSA private encrypt failed") { + text = "Your PIN is incorrect" + } + switch message { + case "Preauthentication failed": + text = "Incorrect username or password." + case "Password has expired": + text = "Password has expired." + default: + break + } + return text } private func closeWindow() { @@ -162,15 +244,22 @@ class KrbAuthViewController: NSViewController, AccountUpdate { let klist = KlistUtil() let tickets = await klist.klist() - // populate popup list if there is more than one account + // Filter out invalid accounts and remove them + let accounts = await accountsManager.accounts + for account in accounts { + let upnComponents = account.upn.split(separator: "@") + if upnComponents.count < 2 || upnComponents[0].isEmpty { + // Invalid account found, remove it + Logger.KrbAuthViewController.debug("Removing invalid account: \(account.displayName)") + await accountsManager.deleteAccount(account: account) + } + } + if await accountsManager.accounts.count > 1 && !prefs.bool(for: .singleUserMode) { accountsList.removeAllItems() for account in await accountsManager.accounts { - if tickets.contains(where: { $0.principal.lowercased() == account.upn.lowercased() }) { - accountsList.addItem(withTitle: account.displayName + " ◀︎") - } else { - accountsList.addItem(withTitle: account.displayName) - } + let title = account.displayName + (tickets.contains(where: { $0.principal.lowercased() == account.upn.lowercased() }) ? " ◀︎" : "") + accountsList.addItem(withTitle: title) } accountsList.addItem(withTitle: "Other...") username.isHidden = true @@ -180,14 +269,15 @@ class KrbAuthViewController: NSViewController, AccountUpdate { return } - // if there is only one account, hide popup list accountsList.isHidden = true username.isHidden = false if let lastUser = prefs.string(for: .lastUser) { let keyUtil = KeychainManager() do { - try password.stringValue = keyUtil.retrievePassword(forUsername: lastUser.lowercased()) ?? "" + let retrievedPassword = try keyUtil.retrievePassword(forUsername: lastUser.lowercased()) ?? "" + password.stringValue = retrievedPassword username.stringValue = lastUser.lowercased() + authenticateButtonText.isEnabled = !retrievedPassword.isEmpty } catch { Logger.KrbAuthViewController.debug("Unable to get user's password") } @@ -195,95 +285,118 @@ class KrbAuthViewController: NSViewController, AccountUpdate { } @objc func popUpChange() async { - if accountsList.selectedItem?.title == "Other..." { - Task { @MainActor in + await MainActor.run { + Logger.KrbAuthViewController.debug("Selected Item is: \(self.accountsList.selectedItem?.title ?? "None")") + + // Clear the password field and disable the authenticate button + password.stringValue = "" + authenticateButtonText.isEnabled = false + + if accountsList.selectedItem?.title == "Other..." { accountsList.isHidden = true username.isHidden = false username.becomeFirstResponder() + return } - } - - for account in await accountsManager.accounts { - if account.displayName == accountsList.selectedItem?.title.replacingOccurrences(of: " ◀︎", with: "") { - if let isInKeychain = account.hasKeychainEntry, isInKeychain { - let keyUtil = KeychainManager() - do { - try password.stringValue = keyUtil.retrievePassword(forUsername: account.upn.lowercased()) ?? "" - } catch { - Logger.KrbAuthViewController.debug("Unable to get user's password") + + guard let selectedTitle = accountsList.selectedItem?.title else { return } + + Task { + let accounts = await accountsManager.accounts + if let account = accounts.first(where: { $0.displayName == selectedTitle.replacingOccurrences(of: " ◀︎", with: "") }) { + if let isInKeychain = account.hasKeychainEntry, isInKeychain { + let keyUtil = KeychainManager() + do { + let retrievedPassword = try keyUtil.retrievePassword(forUsername: account.upn.lowercased()) ?? "" + password.stringValue = retrievedPassword + authenticateButtonText.isEnabled = !retrievedPassword.isEmpty + } catch { + Logger.KrbAuthViewController.debug("Unable to get user's password") + } } } } } - - Task { @MainActor in - password.stringValue = "" + } + + @objc func usernameDidChange() { + // Clear the password field and disable the authenticate button + password.stringValue = "" + authenticateButtonText.isEnabled = false + } + + @objc func passwordDidChange() { + // Enable the authenticate button if the password field is not empty + authenticateButtonText.isEnabled = !password.stringValue.isEmpty + } + + // MARK: - NSTextFieldDelegate + func controlTextDidChange(_ obj: Notification) { + // Enable the authenticate button if the password field is not empty + if obj.object as? NSTextField == password { + authenticateButtonText.isEnabled = !password.stringValue.isEmpty + } + } + + private func showHelpPopover(for sender: NSButton) { + guard let helpPopoverViewController = storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("HelpPopoverViewController")) as? HelpPopoverViewController else { + // Handle the error, e.g., log an error message or show an alert + Logger.authUI.error("Failed to instantiate HelpPopoverViewController") + return } + + let popover = NSPopover() + popover.contentViewController = helpPopoverViewController + helpPopoverViewController.helpText = helpText[sender.tag] + popover.animates = true + popover.show(relativeTo: sender.frame, of: view, preferredEdge: .minY) + popover.behavior = .transient } } +// MARK: - dogeADUserSessionDelegate extension KrbAuthViewController: dogeADUserSessionDelegate { func dogeADAuthenticationSucceded() async { - Logger.authUI.debug("Auth succeded") + Logger.authUI.debug("Auth succeeded") _ = await cliTask("kswitch -p \(self.session?.userPrincipal ?? "")") await session?.userInfo() - - if let principal = session?.userPrincipal { - if let account = await accountsManager.accountForPrincipal(principal: principal) { - let pwm = KeychainManager() - do { - try pwm.saveCredential(forUsername: account.upn.lowercased(), andPassword: password.stringValue) - Logger.authUI.debug("Password updated in keychain") - } catch { - Logger.authUI.debug("Failed saving password in keychain") - } - } else { - let newAccount = DogeAccount(displayName: principal, upn: principal, hasKeychainEntry: prefs.bool(for: .useKeychain)) - let pwm = KeychainManager() - do { - try pwm.saveCredential(forUsername: principal.lowercased(), andPassword: password.stringValue) - Logger.authUI.debug("Password updated in keychain") - } catch { - Logger.authUI.debug("Error saving password in Keychain") - } - await accountsManager.addAccount(account: newAccount) - } - } - NotificationCenter.default.post(name: .nsmNotification, object: nil, userInfo: ["krbAuthenticated": MounterError.krbAuthSuccessful]) + await handleSuccessfulAuthentication() } func dogeADAuthenticationFailed(error: dogeADSessionError, description: String) async { - Logger.authUI.info("Error: \(description, privacy: .public)") - - if error == .UnAuthenticated { - stopOperations() - return - } - - for account in await accountsManager.accounts { - if account.upn.lowercased() == session?.userPrincipal.lowercased() { - let pwm = KeychainManager() - do { - try pwm.removeCredential(forUsername: account.upn.lowercased()) - Logger.authUI.debug("Password removed from Keychain") - } catch { - Logger.authUI.debug("Error removong password from Keychain") + Task { + Logger.authUI.info("Error: \(description, privacy: .public)") + + if error == .UnAuthenticated { + stopOperations() + return + } + + for account in await accountsManager.accounts { + if account.upn.lowercased() == session?.userPrincipal.lowercased() { + let pwm = KeychainManager() + do { + try pwm.removeCredential(forUsername: account.upn.lowercased()) + Logger.authUI.debug("Password removed from Keychain") + } catch { + Logger.authUI.debug("Error removing password from Keychain") + } } } + stopOperations() + showAlert(message: description) } - stopOperations() - showAlert(message: description) } func dogeADUserInformation(user: ADUserRecord) { Logger.authUI.debug("User info: \(user.userPrincipal, privacy: .public)") - // back to the foreground to change the UI - Task { @MainActor in - prefs.setADUserInfo(user: user) - stopOperations() - closeWindow() - } +// Task { @MainActor in +// prefs.setADUserInfo(user: user) +// stopOperations() +// NotificationCenter.default.post(name: Defaults.nsmReconstructMenuTriggerNotification, object: nil) +// self.closeWindow() +// } } } diff --git a/jamf-manifests/Network Share Mounter.json b/jamf-manifests/Network Share Mounter.json index 3defbc8fec31db785b6325109e3fce024ca42516..806f20ad852b396df8fe447f788cb2c866bf0926 100644 --- a/jamf-manifests/Network Share Mounter.json +++ b/jamf-manifests/Network Share Mounter.json @@ -178,12 +178,18 @@ { "enum_titles": [ + "show", "hidden", - "disabled", - "" + "disabled" ], - "infoText": "Key name: menuCheckUpdates" - } + "infoText": "Key name: menuAbout" + }, + "enum": + [ + "show", + "hidden", + "disabled" + ] }, "menuConnectShares": { "title": "Menu ConnectShares", @@ -194,12 +200,18 @@ { "enum_titles": [ + "show", "hidden", - "disabled", - "" + "disabled" ], - "infoText": "Key name: menuCheckUpdates" - } + "infoText": "Key name: menuConnectShares" + }, + "enum": + [ + "show", + "hidden", + "disabled" + ] }, "menuDisconnectShares": { "title": "Menu DisconnectShares", @@ -210,12 +222,18 @@ { "enum_titles": [ + "show", "hidden", - "disabled", - "" + "disabled" ], - "infoText": "Key name: menuCheckUpdates" - } + "infoText": "Key name: menuDisconnectShares" + }, + "enum": + [ + "show", + "hidden", + "disabled" + ] }, "menuCheckUpdates": { "title": "Menu CheckUpdates", @@ -226,12 +244,18 @@ { "enum_titles": [ + "show", "hidden", - "disabled", - "" + "disabled" ], "infoText": "Key name: menuCheckUpdates" - } + }, + "enum": + [ + "show", + "hidden", + "disabled" + ] }, "menuShowSharesMountDir": { "title": "Menu ShowSharesMountDir", @@ -242,12 +266,18 @@ { "enum_titles": [ + "show", "hidden", - "disabled", - "" + "disabled" ], - "infoText": "Key name: menuCheckUpdates" - } + "infoText": "Key name: menuShowSharesMountDir" + }, + "enum": + [ + "show", + "hidden", + "disabled" + ] }, "menuShowShares": { "title": "Menu ShowShares", @@ -258,12 +288,18 @@ { "enum_titles": [ + "show", "hidden", - "disabled", - "" + "disabled" ], - "infoText": "Key name: menuCheckUpdates" - } + "infoText": "Key name: menuShowShares" + }, + "enum": + [ + "show", + "hidden", + "disabled" + ] }, "menuSettings": { "title": "Menu Settings", @@ -274,12 +310,18 @@ { "enum_titles": [ + "show", "hidden", - "disabled", - "" + "disabled" ], - "infoText": "Key name: menuCheckUpdates" - } + "infoText": "Key name: menuSettings" + }, + "enum": + [ + "show", + "hidden", + "disabled" + ] } } } \ No newline at end of file diff --git a/networkShareMounter.xcodeproj/project.pbxproj b/networkShareMounter.xcodeproj/project.pbxproj index c6a3d7c1b7c5d1a433cdad7cd5036f5da338ae93..2f8e8a744d7eb1247a1b278f745ad13c65be5e40 100644 --- a/networkShareMounter.xcodeproj/project.pbxproj +++ b/networkShareMounter.xcodeproj/project.pbxproj @@ -13,8 +13,11 @@ F72B13EA2B332987001BDEEA /* Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72B13E92B332987001BDEEA /* Menu.swift */; }; F739C41A2755297F003A3CC5 /* DefaultValues.plist in Resources */ = {isa = PBXBuildFile; fileRef = F739C418275525BC003A3CC5 /* DefaultValues.plist */; }; F742ACF62861CB62009864DF /* AppStatistics.swift in Sources */ = {isa = PBXBuildFile; fileRef = F742ACF52861CB62009864DF /* AppStatistics.swift */; }; + F7432E722D11A582009B499B /* dogeADAuth in Frameworks */ = {isa = PBXBuildFile; productRef = F7432E712D11A582009B499B /* dogeADAuth */; }; + F7432E752D11A5BF009B499B /* dogeADAuth in Frameworks */ = {isa = PBXBuildFile; productRef = F7432E742D11A5BF009B499B /* dogeADAuth */; }; F747F8642B6F814A00AC0772 /* AuthType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F747F8632B6F814A00AC0772 /* AuthType.swift */; }; F747F8662B6F81D400AC0772 /* MountStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = F747F8652B6F81D400AC0772 /* MountStatus.swift */; }; + F75964682D13E6CA00916D06 /* dogeADAuth in Frameworks */ = {isa = PBXBuildFile; productRef = F75964672D13E6CA00916D06 /* dogeADAuth */; }; F765D0552A852911001D5116 /* ShareManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F765D0542A852911001D5116 /* ShareManager.swift */; }; F765D0572A852935001D5116 /* Share.swift in Sources */ = {isa = PBXBuildFile; fileRef = F765D0562A852935001D5116 /* Share.swift */; }; F77107B7274EC51600556B20 /* Monitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77107B6274EC51600556B20 /* Monitor.swift */; }; @@ -58,7 +61,6 @@ F7EF65FB2B3B5E2300051D44 /* SetupSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7EF65FA2B3B5E2300051D44 /* SetupSession.swift */; }; F7EF66062B3B5F5200051D44 /* NSTaskWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7EF66052B3B5F5200051D44 /* NSTaskWrapper.swift */; }; F7EFD0A22D0B191A0064D038 /* dogeADAuth in Frameworks */ = {isa = PBXBuildFile; productRef = F7EFD0A12D0B191A0064D038 /* dogeADAuth */; }; - F7EFD0A52D0B43A50064D038 /* dogeADAuth in Frameworks */ = {isa = PBXBuildFile; productRef = F7EFD0A42D0B43A50064D038 /* dogeADAuth */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -136,9 +138,11 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F7EFD0A52D0B43A50064D038 /* dogeADAuth in Frameworks */, + F75964682D13E6CA00916D06 /* dogeADAuth in Frameworks */, + F7432E722D11A582009B499B /* dogeADAuth in Frameworks */, F7A7A2192C8F0240006772D4 /* dogeADAuth in Frameworks */, F7E2AF6F2C3061020063960F /* dogeADAuth in Frameworks */, + F7432E752D11A5BF009B499B /* dogeADAuth in Frameworks */, F7885F642C623084000B96AC /* Sparkle in Frameworks */, F7EFD0A22D0B191A0064D038 /* dogeADAuth in Frameworks */, F7C205172C305C8A00902B4E /* dogeADAuth in Frameworks */, @@ -316,7 +320,9 @@ F7A7A2182C8F0240006772D4 /* dogeADAuth */, F7CAE1242CFB879000D0A839 /* Sentry */, F7EFD0A12D0B191A0064D038 /* dogeADAuth */, - F7EFD0A42D0B43A50064D038 /* dogeADAuth */, + F7432E712D11A582009B499B /* dogeADAuth */, + F7432E742D11A5BF009B499B /* dogeADAuth */, + F75964672D13E6CA00916D06 /* dogeADAuth */, ); productName = "Network Share Mounter"; productReference = F79B1595274E722000C322A8 /* Network Share Mounter.app */; @@ -405,7 +411,7 @@ F7BAC10B274FBA2D00B5BFF4 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */, F7885F622C623084000B96AC /* XCRemoteSwiftPackageReference "Sparkle" */, F7CAE1232CFB879000D0A839 /* XCRemoteSwiftPackageReference "sentry-cocoa" */, - F7EFD0A32D0B43A50064D038 /* XCRemoteSwiftPackageReference "dogeadauth" */, + F75964662D13E6CA00916D06 /* XCRemoteSwiftPackageReference "dogeadauth" */, ); productRefGroup = D60FA51D1E7FBE1400D9B5A5 /* Products */; projectDirPath = ""; @@ -698,7 +704,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 196; + CURRENT_PROJECT_VERSION = 199; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=macosx*]" = C8F68RFW4L; @@ -716,7 +722,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.5; - MARKETING_VERSION = 3.1.0; + MARKETING_VERSION = 3.1.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = de.fau.rrze.NetworkShareMounter; @@ -748,7 +754,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 196; + CURRENT_PROJECT_VERSION = 199; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=macosx*]" = C8F68RFW4L; @@ -766,7 +772,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.5; - MARKETING_VERSION = 3.1.0; + MARKETING_VERSION = 3.1.1; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = de.fau.rrze.NetworkShareMounter; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -790,7 +796,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 194; + CURRENT_PROJECT_VERSION = 195; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = C8F68RFW4L; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -821,7 +827,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 194; + CURRENT_PROJECT_VERSION = 195; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = C8F68RFW4L; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -848,7 +854,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 194; + CURRENT_PROJECT_VERSION = 195; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 73H7Y3TZRJ; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -879,7 +885,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 194; + CURRENT_PROJECT_VERSION = 195; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 73H7Y3TZRJ; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -938,6 +944,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + F75964662D13E6CA00916D06 /* XCRemoteSwiftPackageReference "dogeadauth" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://gitlab.rrze.fau.de/faumac/sp/dogeadauth.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.9; + }; + }; F7885F622C623084000B96AC /* XCRemoteSwiftPackageReference "Sparkle" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/sparkle-project/Sparkle"; @@ -962,17 +976,22 @@ minimumVersion = 8.41.0; }; }; - F7EFD0A32D0B43A50064D038 /* XCRemoteSwiftPackageReference "dogeadauth" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://gitlab.rrze.fau.de/faumac/sp/dogeadauth.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.0.8; - }; - }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + F7432E712D11A582009B499B /* dogeADAuth */ = { + isa = XCSwiftPackageProductDependency; + productName = dogeADAuth; + }; + F7432E742D11A5BF009B499B /* dogeADAuth */ = { + isa = XCSwiftPackageProductDependency; + productName = dogeADAuth; + }; + F75964672D13E6CA00916D06 /* dogeADAuth */ = { + isa = XCSwiftPackageProductDependency; + package = F75964662D13E6CA00916D06 /* XCRemoteSwiftPackageReference "dogeadauth" */; + productName = dogeADAuth; + }; F7885F632C623084000B96AC /* Sparkle */ = { isa = XCSwiftPackageProductDependency; package = F7885F622C623084000B96AC /* XCRemoteSwiftPackageReference "Sparkle" */; @@ -1004,11 +1023,6 @@ isa = XCSwiftPackageProductDependency; productName = dogeADAuth; }; - F7EFD0A42D0B43A50064D038 /* dogeADAuth */ = { - isa = XCSwiftPackageProductDependency; - package = F7EFD0A32D0B43A50064D038 /* XCRemoteSwiftPackageReference "dogeadauth" */; - productName = dogeADAuth; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = D60FA5141E7FBE1300D9B5A5 /* Project object */;