Device Manager: Verify session (#6832)

* Initial implementation
* Add verificationState to UserSessionInfo
* Listen for changes device changes in the service.
This commit is contained in:
Doug
2022-10-11 16:11:52 +01:00
committed by GitHub
parent 6c9a058b93
commit 6a0112aa95
33 changed files with 534 additions and 114 deletions
@@ -44,6 +44,16 @@ class UserSessionsDataProvider: UserSessionsDataProviderProtocol {
session.crypto.device(withDeviceId: deviceId, ofUser: userId)
}
func verificationState(for deviceInfo: MXDeviceInfo?) -> UserSessionInfo.VerificationState {
guard let deviceInfo = deviceInfo else { return .unknown }
guard session.crypto?.crossSigning?.canCrossSign == true else {
return deviceInfo.deviceId == session.myDeviceId ? .unverified : .unknown
}
return deviceInfo.trustLevel.isVerified ? .verified : .unverified
}
func accountData(for eventType: String) -> [AnyHashable: Any]? {
session.accountData.accountData(forEventType: eventType)
}
@@ -28,6 +28,8 @@ protocol UserSessionsDataProviderProtocol {
func device(withDeviceId deviceId: String, ofUser userId: String) -> MXDeviceInfo?
func verificationState(for deviceInfo: MXDeviceInfo?) -> UserSessionInfo.VerificationState
func accountData(for eventType: String) -> [AnyHashable: Any]?
func qrLoginAvailable() async throws -> Bool
@@ -22,6 +22,7 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
private static let inactiveSessionDurationTreshold: TimeInterval = 90 * 86400
private let dataProvider: UserSessionsDataProviderProtocol
private var cancellables: Set<AnyCancellable> = []
private(set) var overviewDataPublisher: CurrentValueSubject<UserSessionsOverviewData, Never>
private(set) var sessionInfos: [UserSessionInfo]
@@ -36,6 +37,7 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
linkDeviceEnabled: false))
sessionInfos = []
setupInitialOverviewData()
listenForSessionUpdates()
}
// MARK: - Public
@@ -68,13 +70,31 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
// MARK: - Private
private func listenForSessionUpdates() {
NotificationCenter.default.publisher(for: .MXDeviceInfoTrustLevelDidChange)
.sink { [weak self] _ in
self?.updateOverviewData { _ in }
}
.store(in: &cancellables)
NotificationCenter.default.publisher(for: .MXDeviceListDidUpdateUsersDevices)
.sink { [weak self] _ in
self?.updateOverviewData { _ in }
}
.store(in: &cancellables)
NotificationCenter.default.publisher(for: .MXCrossSigningInfoTrustLevelDidChange)
.sink { [weak self] _ in
self?.updateOverviewData { _ in }
}
.store(in: &cancellables)
}
private func setupInitialOverviewData() {
guard let currentSessionInfo = getCurrentSessionInfo() else {
return
}
overviewDataPublisher = .init(UserSessionsOverviewData(currentSession: currentSessionInfo,
unverifiedSessions: currentSessionInfo.isVerified ? [] : [currentSessionInfo],
unverifiedSessions: currentSessionInfo.verificationState == .verified ? [] : [currentSessionInfo],
inactiveSessions: currentSessionInfo.isActive ? [] : [currentSessionInfo],
otherSessions: [],
linkDeviceEnabled: false))
@@ -97,14 +117,15 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
private func sessionsOverviewData(from allSessions: [UserSessionInfo],
linkDeviceEnabled: Bool) -> UserSessionsOverviewData {
UserSessionsOverviewData(currentSession: allSessions.filter(\.isCurrent).first,
unverifiedSessions: allSessions.filter { !$0.isVerified },
unverifiedSessions: allSessions.filter { $0.verificationState != .verified },
inactiveSessions: allSessions.filter { !$0.isActive },
otherSessions: allSessions.filter { !$0.isCurrent },
linkDeviceEnabled: linkDeviceEnabled)
}
private func sessionInfo(from device: MXDevice, isCurrentSession: Bool) -> UserSessionInfo {
let isSessionVerified = deviceInfo(for: device.deviceId)?.trustLevel.isVerified ?? false
let deviceInfo = deviceInfo(for: device.deviceId)
let verificationState = dataProvider.verificationState(for: deviceInfo)
let eventType = kMXAccountDataTypeClientInformation + "." + device.deviceId
let appData = dataProvider.accountData(for: eventType)
@@ -123,7 +144,7 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
return UserSessionInfo(withDevice: device,
applicationData: appData as? [String: String],
userAgent: userAgent,
isSessionVerified: isSessionVerified,
verificationState: verificationState,
isActive: isSessionActive,
isCurrent: isCurrentSession)
}
@@ -141,13 +162,13 @@ extension UserSessionInfo {
init(withDevice device: MXDevice,
applicationData: [String: String]?,
userAgent: UserAgent?,
isSessionVerified: Bool,
verificationState: VerificationState,
isActive: Bool,
isCurrent: Bool) {
self.init(id: device.deviceId,
name: device.displayName,
deviceType: userAgent?.deviceType ?? .unknown,
isVerified: isSessionVerified,
verificationState: verificationState,
lastSeenIP: device.lastSeenIp,
lastSeenTimestamp: device.lastSeenTs > 0 ? TimeInterval(device.lastSeenTs / 1000) : nil,
applicationName: applicationData?["name"],
@@ -80,13 +80,13 @@ class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
otherSessions.first { $0.id == sessionId }
}
// MARK: - Private
// MARK: - Private
private var mockCurrentSession: UserSessionInfo {
UserSessionInfo(id: "alice",
name: "iOS",
deviceType: .mobile,
isVerified: mode == .currentSessionVerified,
verificationState: mode == .currentSessionVerified ? .verified : .unverified,
lastSeenIP: "10.0.0.10",
lastSeenTimestamp: nil,
applicationName: "Element iOS",
@@ -105,7 +105,7 @@ class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
[UserSessionInfo(id: "1 verified: \(verified) active: \(active)",
name: "macOS verified: \(verified) active: \(active)",
deviceType: .desktop,
isVerified: verified,
verificationState: verified ? .verified : .unverified,
lastSeenIP: "1.0.0.1",
lastSeenTimestamp: Date().timeIntervalSince1970 - 8_000_000,
applicationName: "Element MacOS",
@@ -121,7 +121,7 @@ class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
UserSessionInfo(id: "2 verified: \(verified) active: \(active)",
name: "Firefox on Windows verified: \(verified) active: \(active)",
deviceType: .web,
isVerified: verified,
verificationState: verified ? .verified : .unverified,
lastSeenIP: "2.0.0.2",
lastSeenTimestamp: Date().timeIntervalSince1970 - 100,
applicationName: "Element Web",
@@ -137,7 +137,7 @@ class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
UserSessionInfo(id: "3 verified: \(verified) active: \(active)",
name: "Android verified: \(verified) active: \(active)",
deviceType: .mobile,
isVerified: verified,
verificationState: verified ? .verified : .unverified,
lastSeenIP: "3.0.0.3",
lastSeenTimestamp: Date().timeIntervalSince1970 - 10,
applicationName: "Element Android",