diff --git a/SessionMessagingKit/Jobs/DisplayPictureDownloadJob.swift b/SessionMessagingKit/Jobs/DisplayPictureDownloadJob.swift index dac761d872..edae3c0108 100644 --- a/SessionMessagingKit/Jobs/DisplayPictureDownloadJob.swift +++ b/SessionMessagingKit/Jobs/DisplayPictureDownloadJob.swift @@ -48,7 +48,7 @@ public enum DisplayPictureDownloadJob: JobExecutor { using: dependencies ) - case .community(let fileId, let roomToken, let server): + case .community(let fileId, let roomToken, let server, let skipAuthentication): guard let info: LibSession.OpenGroupCapabilityInfo = try? LibSession.OpenGroupCapabilityInfo .fetchOne(db, id: OpenGroup.idFor(roomToken: roomToken, server: server)) @@ -58,6 +58,7 @@ public enum DisplayPictureDownloadJob: JobExecutor { fileId: fileId, roomToken: roomToken, authMethod: Authentication.community(info: info), + skipAuthentication: skipAuthentication, using: dependencies ) } @@ -206,7 +207,7 @@ public enum DisplayPictureDownloadJob: JobExecutor { ) db.addConversationEvent(id: id, type: .updated(.displayPictureUrl(url))) - case .community(_, let roomToken, let server): + case .community(_, let roomToken, let server, _): _ = try? OpenGroup .filter(id: OpenGroup.idFor(roomToken: roomToken, server: server)) .updateAllAndConfig( @@ -228,7 +229,7 @@ extension DisplayPictureDownloadJob { public enum Target: Codable, Hashable, CustomStringConvertible { case profile(id: String, url: String, encryptionKey: Data) case group(id: String, url: String, encryptionKey: Data) - case community(imageId: String, roomToken: String, server: String) + case community(imageId: String, roomToken: String, server: String, skipAuthentication: Bool = false) var isValid: Bool { switch self { @@ -239,7 +240,7 @@ extension DisplayPictureDownloadJob { encryptionKey.count == DisplayPictureManager.aes256KeyByteLength ) - case .community(let imageId, _, _): return !imageId.isEmpty + case .community(let imageId, _, _, _): return !imageId.isEmpty } } @@ -249,7 +250,7 @@ extension DisplayPictureDownloadJob { switch self { case .profile(let id, _, _): return "profile: \(id)" case .group(let id, _, _): return "group: \(id)" - case .community(_, let roomToken, let server): return "room: \(roomToken) on server: \(server)" + case .community(_, let roomToken, let server, _): return "room: \(roomToken) on server: \(server)" } } } @@ -274,11 +275,12 @@ extension DisplayPictureDownloadJob { self.target = { switch target { - case .community(let imageId, let roomToken, let server): + case .community(let imageId, let roomToken, let server, let skipAuthentication): return .community( imageId: imageId, roomToken: roomToken, - server: server.lowercased() // Always in lowercase on `OpenGroup` + server: server.lowercased(), // Always in lowercase on `OpenGroup` + skipAuthentication: skipAuthentication ) default: return target @@ -358,7 +360,7 @@ extension DisplayPictureDownloadJob { return (url == latestDisplayPictureUrl) - case .community(let imageId, let roomToken, let server): + case .community(let imageId, let roomToken, let server, _): guard let latestImageId: String = try? OpenGroup .select(.imageId) diff --git a/SessionMessagingKit/Jobs/RetrieveDefaultOpenGroupRoomsJob.swift b/SessionMessagingKit/Jobs/RetrieveDefaultOpenGroupRoomsJob.swift index 1dac5e6dd7..e34c48694b 100644 --- a/SessionMessagingKit/Jobs/RetrieveDefaultOpenGroupRoomsJob.swift +++ b/SessionMessagingKit/Jobs/RetrieveDefaultOpenGroupRoomsJob.swift @@ -72,6 +72,7 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { .tryFlatMap { [dependencies] authMethod -> AnyPublisher<(ResponseInfoType, Network.SOGS.CapabilitiesAndRoomsResponse), Error> in try Network.SOGS.preparedCapabilitiesAndRooms( authMethod: authMethod, + skipAuthentication: true, using: dependencies ).send(using: dependencies) } @@ -157,7 +158,8 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { target: .community( imageId: imageId, roomToken: room.token, - server: Network.SOGS.defaultServer + server: Network.SOGS.defaultServer, + skipAuthentication: true ), timestamp: (dependencies[cache: .snodeAPI].currentOffsetTimestampMs() / 1000) ) diff --git a/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift b/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift index 1a0770edc9..42ff8e59cc 100644 --- a/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift +++ b/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift @@ -265,6 +265,7 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { ), forceBlinded: false ), + skipAuthentication: true, using: dependencies ) } @@ -286,6 +287,8 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { requestAndPathBuildTimeout: expectedRequest.requestAndPathBuildTimeout ) }) + + expect(expectedRequest?.headers).to(beEmpty()) } // MARK: -- will retry 8 times before it fails @@ -440,7 +443,8 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { target: .community( imageId: "12", roomToken: "testRoom2", - server: Network.SOGS.defaultServer + server: Network.SOGS.defaultServer, + skipAuthentication: true ), timestamp: 1234567890 ) @@ -487,7 +491,8 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { target: .community( imageId: "12", roomToken: "testRoom2", - server: Network.SOGS.defaultServer + server: Network.SOGS.defaultServer, + skipAuthentication: true ), timestamp: 1234567890 ) diff --git a/SessionNetworkingKit/SOGS/SOGSAPI.swift b/SessionNetworkingKit/SOGS/SOGSAPI.swift index fcdc911e9d..69ec3a4465 100644 --- a/SessionNetworkingKit/SOGS/SOGSAPI.swift +++ b/SessionNetworkingKit/SOGS/SOGSAPI.swift @@ -156,9 +156,10 @@ public extension Network.SOGS { private static func preparedSequence( requests: [any ErasedPreparedRequest], authMethod: AuthenticationMethod, + skipAuthentication: Bool = false, using dependencies: Dependencies ) throws -> Network.PreparedRequest> { - return try Network.PreparedRequest( + let preparedRequest = try Network.PreparedRequest( request: Request( method: .post, endpoint: Endpoint.sequence, @@ -169,7 +170,7 @@ public extension Network.SOGS { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: Network.SOGS.signRequest, using: dependencies) + return skipAuthentication ? preparedRequest : try preparedRequest.signed(with: Network.SOGS.signRequest, using: dependencies) } // MARK: - Capabilities @@ -183,9 +184,10 @@ public extension Network.SOGS { /// could return: `{"capabilities": ["sogs", "batch"], "missing": ["magic"]}` static func preparedCapabilities( authMethod: AuthenticationMethod, + skipAuthentication: Bool = false, using dependencies: Dependencies ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( + let preparedRequest = try Network.PreparedRequest( request: Request( endpoint: .capabilities, authMethod: authMethod @@ -194,7 +196,7 @@ public extension Network.SOGS { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: Network.SOGS.signRequest, using: dependencies) + return skipAuthentication ? preparedRequest : try preparedRequest.signed(with: Network.SOGS.signRequest, using: dependencies) } // MARK: - Room @@ -204,9 +206,10 @@ public extension Network.SOGS { /// Rooms to which the user does not have access (e.g. because they are banned, or the room has restricted access permissions) are not included static func preparedRooms( authMethod: AuthenticationMethod, + skipAuthentication: Bool = false, using dependencies: Dependencies ) throws -> Network.PreparedRequest<[Room]> { - return try Network.PreparedRequest( + let preparedRequest = try Network.PreparedRequest( request: Request( endpoint: .rooms, authMethod: authMethod @@ -215,7 +218,7 @@ public extension Network.SOGS { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: Network.SOGS.signRequest, using: dependencies) + return skipAuthentication ? preparedRequest : try preparedRequest.signed(with: Network.SOGS.signRequest, using: dependencies) } /// Returns the details of a single room @@ -317,20 +320,25 @@ public extension Network.SOGS { /// methods for the documented behaviour of each method static func preparedCapabilitiesAndRooms( authMethod: AuthenticationMethod, + skipAuthentication: Bool = false, using dependencies: Dependencies ) throws -> Network.PreparedRequest { - return try Network.SOGS + let preparedRequest = try Network.SOGS .preparedSequence( requests: [ // Get the latest capabilities for the server (in case it's a new server or the // cached ones are stale) - preparedCapabilities(authMethod: authMethod, using: dependencies), - preparedRooms(authMethod: authMethod, using: dependencies) + preparedCapabilities(authMethod: authMethod, skipAuthentication: skipAuthentication, using: dependencies), + preparedRooms(authMethod: authMethod, skipAuthentication: skipAuthentication, using: dependencies) ], authMethod: authMethod, + skipAuthentication: skipAuthentication, using: dependencies ) - .signed(with: Network.SOGS.signRequest, using: dependencies) + + let finalRequest = skipAuthentication ? preparedRequest : try preparedRequest.signed(with: Network.SOGS.signRequest, using: dependencies) + + return finalRequest .tryMap { (info: ResponseInfoType, response: Network.BatchResponseMap) -> CapabilitiesAndRoomsResponse in let maybeCapabilities: Network.BatchSubResponse? = (response[.capabilities] as? Network.BatchSubResponse) let maybeRooms: Network.BatchSubResponse<[Room]>? = response.data @@ -835,9 +843,10 @@ public extension Network.SOGS { fileId: String, roomToken: String, authMethod: AuthenticationMethod, + skipAuthentication: Bool = false, using dependencies: Dependencies ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( + let preparedRequest = try Network.PreparedRequest( request: Request( endpoint: .roomFileIndividual(roomToken, fileId), authMethod: authMethod @@ -847,7 +856,7 @@ public extension Network.SOGS { requestTimeout: Network.fileDownloadTimeout, using: dependencies ) - .signed(with: Network.SOGS.signRequest, using: dependencies) + return skipAuthentication ? preparedRequest : try preparedRequest.signed(with: Network.SOGS.signRequest, using: dependencies) } // MARK: - Inbox/Outbox (Message Requests) diff --git a/SessionNetworkingKitTests/SOGS/SOGSAPISpec.swift b/SessionNetworkingKitTests/SOGS/SOGSAPISpec.swift index 877b6f31da..627199f818 100644 --- a/SessionNetworkingKitTests/SOGS/SOGSAPISpec.swift +++ b/SessionNetworkingKitTests/SOGS/SOGSAPISpec.swift @@ -751,6 +751,44 @@ class SOGSAPISpec: QuickSpec { expect(preparedRequest?.path).to(equal("/sequence")) expect(preparedRequest?.method.rawValue).to(equal("POST")) + + expect(preparedRequest?.headers).toNot(beEmpty()) + expect(preparedRequest?.headers).to(equal([ + HTTPHeader.sogsNonce: "pK6YRtQApl4NhECGizF0Cg==", + HTTPHeader.sogsTimestamp: "1234567890", + HTTPHeader.sogsSignature: "VGVzdFNvZ3NTaWduYXR1cmU=", + HTTPHeader.sogsPubKey: "1588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b" + ])) + } + + // MARK: ---- generates the request correctly and skips adding request headers + it("generates the request correctly and skips adding request headers") { + expect { + preparedRequest = try OpenGroupAPI.preparedCapabilitiesAndRooms( + authMethod: Authentication.community( + info: LibSession.OpenGroupCapabilityInfo( + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + capabilities: [] + ), + forceBlinded: false + ), + skipAuthentication: true, + using: dependencies + ) + }.toNot(throwError()) + + expect(preparedRequest?.batchEndpoints.count).to(equal(2)) + expect(preparedRequest?.batchEndpoints[test: 0].asType(OpenGroupAPI.Endpoint.self)) + .to(equal(.capabilities)) + expect(preparedRequest?.batchEndpoints[test: 1].asType(OpenGroupAPI.Endpoint.self)) + .to(equal(.rooms)) + + expect(preparedRequest?.path).to(equal("/sequence")) + expect(preparedRequest?.method.rawValue).to(equal("POST")) + + expect(preparedRequest?.headers).to(beEmpty()) } // MARK: ---- processes a valid response correctly @@ -1578,6 +1616,31 @@ class SOGSAPISpec: QuickSpec { ])) } + // MARK: ---- generates the download destination correctly when given an id and skips adding request headers + it("generates the download destination correctly when given an id and skips adding request headers") { + expect { + preparedRequest = try OpenGroupAPI.preparedDownload( + fileId: "1", + roomToken: "roomToken", + authMethod: Authentication.community( + info: LibSession.OpenGroupCapabilityInfo( + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + capabilities: [] + ), + forceBlinded: false + ), + skipAuthentication: true, + using: dependencies + ) + }.toNot(throwError()) + + expect(preparedRequest?.path).to(equal("/room/roomToken/file/1")) + expect(preparedRequest?.method.rawValue).to(equal("GET")) + expect(preparedRequest?.headers).to(beEmpty()) + } + // MARK: ---- generates the download request correctly when given a URL it("generates the download request correctly when given a URL") { expect { @@ -2203,6 +2266,40 @@ class SOGSAPISpec: QuickSpec { .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) + + expect(preparedRequest?.headers).toNot(beEmpty()) + + expect(response).toNot(beNil()) + expect(error).to(beNil()) + } + + // MARK: ---- triggers sending correctly without headers + it("triggers sending correctly without headers") { + var response: (info: ResponseInfoType, data: [OpenGroupAPI.Room])? + + expect { + preparedRequest = try OpenGroupAPI.preparedRooms( + authMethod: Authentication.community( + info: LibSession.OpenGroupCapabilityInfo( + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + capabilities: [] + ), + forceBlinded: false + ), + skipAuthentication: true, + using: dependencies + ) + }.toNot(throwError()) + + preparedRequest? + .send(using: dependencies) + .handleEvents(receiveOutput: { result in response = result }) + .mapError { error.setting(to: $0) } + .sinkAndStore(in: &disposables) + + expect(preparedRequest?.headers).to(beEmpty()) expect(response).toNot(beNil()) expect(error).to(beNil())