Merge pull request #339 from oxen-io/open-groups

Open Group Fixes
pull/346/head
Niels Andriesse 4 years ago committed by GitHub
commit a53eda40f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5264,7 +5264,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 162;
CURRENT_PROJECT_VERSION = 163;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -5333,7 +5333,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 162;
CURRENT_PROJECT_VERSION = 163;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO;
@ -5394,7 +5394,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 162;
CURRENT_PROJECT_VERSION = 163;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -5464,7 +5464,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 162;
CURRENT_PROJECT_VERSION = 163;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO;
@ -6483,7 +6483,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 162;
CURRENT_PROJECT_VERSION = 163;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -6551,7 +6551,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 162;
CURRENT_PROJECT_VERSION = 163;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",

@ -1528,15 +1528,17 @@ typedef enum : NSUInteger {
[self showDetailViewForViewItem:conversationViewItem];
}
- (void)report:(id<ConversationViewItem>)conversationViewItem
- (void)banUser:(id<ConversationViewItem>)conversationViewItem
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Report?" message:@"If the message is found to violate the Session Public Chat code of conduct it will be removed." preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
uint64_t messageID = 0;
if ([conversationViewItem.interaction isKindOfClass:TSMessage.class]) {
messageID = ((TSMessage *)conversationViewItem.interaction).openGroupServerMessageID;
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Ban This User?" message:nil preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
NSString* publicKey;
if ([conversationViewItem.interaction isKindOfClass:TSIncomingMessage.class]) {
publicKey = ((TSIncomingMessage *)conversationViewItem.interaction).authorId;
}
[SNOpenGroupAPI reportMessageWithID:messageID inChannel:1 onServer:@"https://chat.getsession.org"];
SNOpenGroup *openGroup = [LKStorage.shared getOpenGroupForThreadID:self.thread.uniqueId];
if (openGroup == nil) return;
[[SNOpenGroupAPI banPublicKey:publicKey fromServer:openGroup.server] retainUntilComplete];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alert animated:YES completion:nil];

@ -67,6 +67,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
@property (nonatomic, readonly) BOOL isGroupThread;
@property (nonatomic, readonly) BOOL userCanDeleteGroupMessage;
@property (nonatomic, readonly) BOOL userHasModerationPermission;
@property (nonatomic, readonly) BOOL hasBodyText;

@ -1162,30 +1162,45 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
- (BOOL)userCanDeleteGroupMessage
{
if (!self.isGroupThread) return false;
// Ensure the thread is a public chat and not an RSS feed
TSGroupThread *groupThread = (TSGroupThread *)self.interaction.thread;
// Only allow deletion on incoming and outgoing messages
OWSInteractionType interationType = self.interaction.interactionType;
if (interationType != OWSInteractionType_OutgoingMessage && interationType != OWSInteractionType_IncomingMessage) return false;
// Make sure it's a public chat message
// Make sure it's an open group message
TSMessage *message = (TSMessage *)self.interaction;
if (!message.isOpenGroupMessage) return true;
// Ensure we have the details needed to contact the server
SNOpenGroup *publicChat = [LKStorage.shared getOpenGroupForThreadID:groupThread.uniqueId];
if (publicChat == nil) return true;
SNOpenGroup *openGroup = [LKStorage.shared getOpenGroupForThreadID:groupThread.uniqueId];
if (openGroup == nil) return true;
if (interationType == OWSInteractionType_IncomingMessage) {
// Only allow deletion on incoming messages if the user has moderation permission
return [SNOpenGroupAPI isUserModerator:[SNGeneralUtilities getUserPublicKey] forChannel:publicChat.channel onServer:publicChat.server];
return [SNOpenGroupAPI isUserModerator:[SNGeneralUtilities getUserPublicKey] forChannel:openGroup.channel onServer:openGroup.server];
} else {
return YES;
}
}
- (BOOL)userHasModerationPermission
{
if (!self.isGroupThread) return false;
TSGroupThread *groupThread = (TSGroupThread *)self.interaction.thread;
// Make sure it's an open group message
TSMessage *message = (TSMessage *)self.interaction;
if (!message.isOpenGroupMessage) return false;
// Ensure we have the details needed to contact the server
SNOpenGroup *openGroup = [LKStorage.shared getOpenGroupForThreadID:groupThread.uniqueId];
if (openGroup == nil) return false;
// Check that we're a moderator
return [SNOpenGroupAPI isUserModerator:[SNGeneralUtilities getUserPublicKey] forChannel:openGroup.channel onServer:openGroup.server];
}
@end
NS_ASSUME_NONNULL_END

@ -6,7 +6,7 @@ import Foundation
@objc
protocol MessageActionsDelegate: class {
func report(_ conversationViewItem: ConversationViewItem)
func banUser(_ conversationViewItem: ConversationViewItem)
func messageActionsShowDetailsForItem(_ conversationViewItem: ConversationViewItem)
func messageActionsReplyToItem(_ conversationViewItem: ConversationViewItem)
func copyPublicKey(for conversationViewItem: ConversationViewItem)
@ -45,14 +45,6 @@ struct MessageActionBuilder {
block: { [weak delegate] _ in delegate?.messageActionsShowDetailsForItem(conversationViewItem) }
)
}
static func report(_ conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction {
return MenuAction(image: #imageLiteral(resourceName: "Flag"),
title: NSLocalizedString("Report", comment: ""),
subtitle: nil,
block: { [weak delegate] _ in delegate?.report(conversationViewItem) }
)
}
static func deleteMessage(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction {
return MenuAction(image: #imageLiteral(resourceName: "ic_trash"),
@ -61,6 +53,14 @@ struct MessageActionBuilder {
block: { _ in conversationViewItem.deleteAction() }
)
}
static func banUser(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction {
return MenuAction(image: #imageLiteral(resourceName: "ic_block"),
title: "Ban User",
subtitle: nil,
block: { [weak delegate] _ in delegate?.banUser(conversationViewItem) }
)
}
static func copyMedia(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction {
return MenuAction(image: #imageLiteral(resourceName: "ic_copy"),
@ -108,10 +108,9 @@ class ConversationViewItemActions: NSObject {
actions.append(deleteAction)
}
if isGroup && conversationViewItem.interaction.thread.name() == "Loki Public Chat"
|| conversationViewItem.interaction.thread.name() == "Session Public Chat" {
let reportAction = MessageActionBuilder.report(conversationViewItem, delegate: delegate)
actions.append(reportAction)
if isGroup && conversationViewItem.interaction is TSIncomingMessage && conversationViewItem.userHasModerationPermission {
let banAction = MessageActionBuilder.banUser(conversationViewItem: conversationViewItem, delegate: delegate)
actions.append(banAction)
}
let showDetailsAction = MessageActionBuilder.showDetails(conversationViewItem: conversationViewItem, delegate: delegate)
@ -152,10 +151,9 @@ class ConversationViewItemActions: NSObject {
actions.append(deleteAction)
}
if isGroup && conversationViewItem.interaction.thread.name() == "Loki Public Chat"
|| conversationViewItem.interaction.thread.name() == "Session Public Chat" {
let reportAction = MessageActionBuilder.report(conversationViewItem, delegate: delegate)
actions.append(reportAction)
if isGroup && conversationViewItem.interaction is TSIncomingMessage && conversationViewItem.userHasModerationPermission {
let banAction = MessageActionBuilder.banUser(conversationViewItem: conversationViewItem, delegate: delegate)
actions.append(banAction)
}
let showDetailsAction = MessageActionBuilder.showDetails(conversationViewItem: conversationViewItem, delegate: delegate)
@ -185,10 +183,9 @@ class ConversationViewItemActions: NSObject {
actions.append(deleteAction)
}
if isGroup && conversationViewItem.interaction.thread.name() == "Loki Public Chat"
|| conversationViewItem.interaction.thread.name() == "Session Public Chat" {
let reportAction = MessageActionBuilder.report(conversationViewItem, delegate: delegate)
actions.append(reportAction)
if isGroup && conversationViewItem.interaction is TSIncomingMessage && conversationViewItem.userHasModerationPermission {
let banAction = MessageActionBuilder.banUser(conversationViewItem: conversationViewItem, delegate: delegate)
actions.append(banAction)
}
let showDetailsAction = MessageActionBuilder.showDetails(conversationViewItem: conversationViewItem, delegate: delegate)

@ -190,14 +190,18 @@ extension Storage {
(transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: openGroupID, inCollection: Storage.openGroupUserCountCollection)
}
public func getIDForMessage(withServerID serverID: UInt64) -> UInt64? {
var result: UInt64? = nil
public func getIDForMessage(withServerID serverID: UInt64) -> String? {
var result: String? = nil
Storage.read { transaction in
result = transaction.object(forKey: String(serverID), inCollection: Storage.openGroupMessageIDCollection) as? UInt64
result = transaction.object(forKey: String(serverID), inCollection: Storage.openGroupMessageIDCollection) as? String
}
return result
}
public func setIDForMessage(withServerID serverID: UInt64, to messageID: String, using transaction: Any) {
(transaction as! YapDatabaseReadWriteTransaction).setObject(messageID, forKey: String(serverID), inCollection: Storage.openGroupMessageIDCollection)
}
public func setOpenGroupDisplayName(to displayName: String, for publicKey: String, inOpenGroupWithID openGroupID: String, using transaction: Any) {
let collection = openGroupID
(transaction as! YapDatabaseReadWriteTransaction).setObject(displayName, forKey: publicKey, inCollection: collection)

@ -218,13 +218,13 @@ public final class OpenGroupAPI : DotNetAPI {
@objc(deleteMessageWithID:forGroup:onServer:isSentByUser:)
public static func objc_deleteMessage(with messageID: UInt, for group: UInt64, on server: String, isSentByUser: Bool) -> AnyPromise {
return AnyPromise.from(deleteMessage(with: messageID, for: group, on: server, isSentByUser: isSentByUser))
return AnyPromise.from(deleteMessage(with: messageID, for: group, on: server, wasSentByUser: isSentByUser))
}
public static func deleteMessage(with messageID: UInt, for channel: UInt64, on server: String, isSentByUser: Bool) -> Promise<Void> {
let isModerationRequest = !isSentByUser
public static func deleteMessage(with messageID: UInt, for channel: UInt64, on server: String, wasSentByUser: Bool) -> Promise<Void> {
let isModerationRequest = !wasSentByUser
SNLog("Deleting message with ID: \(messageID) for open group channel with ID: \(channel) on server: \(server) (isModerationRequest = \(isModerationRequest)).")
let urlAsString = isSentByUser ? "\(server)/channels/\(channel)/messages/\(messageID)" : "\(server)/loki/v1/moderation/message/\(messageID)"
let urlAsString = wasSentByUser ? "\(server)/channels/\(channel)/messages/\(messageID)" : "\(server)/loki/v1/moderation/message/\(messageID)"
return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) {
getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in
getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<Void> in
@ -238,6 +238,33 @@ public final class OpenGroupAPI : DotNetAPI {
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
}
// MARK: Banning
@objc(banPublicKey:fromServer:)
public static func objc_ban(_ publicKey: String, from server: String) -> AnyPromise {
return AnyPromise.from(ban(publicKey, from: server))
}
public static func ban(_ publicKey: String, from server: String) -> Promise<Void> {
SNLog("Banning user with ID: \(publicKey) from server: \(server).")
return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) {
getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in
getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<Void> in
let url = URL(string: "\(server)/loki/v1/moderation/blacklist/@\(publicKey)")!
let request = TSRequest(url: url, method: "POST", parameters: [:])
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
let promise = OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey, isJSONRequired: false)
promise.done(on: DispatchQueue.global(qos: .default)) { _ -> Void in
SNLog("Banned user with ID: \(publicKey) from server: \(server).")
}
promise.catch(on: DispatchQueue.main) { error in
print(error)
}
return promise.map { _ in }
}
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
}
// MARK: Display Name & Profile Picture
public static func getDisplayNames(for channel: UInt64, on server: String) -> Promise<Void> {

@ -215,6 +215,10 @@ extension MessageReceiver {
if isMainAppAndActive {
cancelTypingIndicatorsIfNeeded(for: message.sender!)
}
// Keep track of the open group server message ID message ID relationship
if let serverID = message.openGroupServerMessageID {
storage.setIDForMessage(withServerID: serverID, to: tsIncomingMessageID, using: transaction)
}
// Notify the user if needed
guard (isMainAppAndActive || isBackgroundPoll), let tsIncomingMessage = TSIncomingMessage.fetch(uniqueId: tsIncomingMessageID, transaction: transaction),
let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) else { return tsIncomingMessageID }

@ -342,15 +342,21 @@ public final class MessageSender : NSObject {
public static func handleSuccessfulMessageSend(_ message: Message, to destination: Message.Destination, using transaction: Any) {
guard let tsMessage = TSOutgoingMessage.find(withTimestamp: message.sentTimestamp!) else { return }
tsMessage.openGroupServerMessageID = message.openGroupServerMessageID ?? 0
let storage = SNMessagingKitConfiguration.shared.storage
let transaction = transaction as! YapDatabaseReadWriteTransaction
tsMessage.save(with: transaction)
if let serverID = message.openGroupServerMessageID {
storage.setIDForMessage(withServerID: serverID, to: tsMessage.uniqueId!, using: transaction)
}
var recipients = [ message.recipient! ]
if case .closedGroup(_) = destination, let threadID = message.threadID, // threadID should always be set at this point
let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction as! YapDatabaseReadTransaction), thread.isClosedGroup {
let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction), thread.isClosedGroup {
recipients = thread.groupModel.groupMemberIds
}
recipients.forEach { recipient in
tsMessage.update(withSentRecipient: recipient, wasSentByUD: true, transaction: transaction as! YapDatabaseReadWriteTransaction)
tsMessage.update(withSentRecipient: recipient, wasSentByUD: true, transaction: transaction)
}
OWSDisappearingMessagesJob.shared().startAnyExpiration(for: tsMessage, expirationStartedAt: NSDate.millisecondTimestamp(), transaction: transaction as! YapDatabaseReadWriteTransaction)
OWSDisappearingMessagesJob.shared().startAnyExpiration(for: tsMessage, expirationStartedAt: NSDate.millisecondTimestamp(), transaction: transaction)
}
public static func handleFailedMessageSend(_ message: Message, with error: Swift.Error, using transaction: Any) {

@ -19,7 +19,7 @@ public final class OpenGroupPoller : NSObject {
// MARK: Settings
private let pollForNewMessagesInterval: TimeInterval = 4
private let pollForDeletedMessagesInterval: TimeInterval = 60
private let pollForDeletedMessagesInterval: TimeInterval = 30
private let pollForModeratorsInterval: TimeInterval = 10 * 60
// MARK: Lifecycle
@ -193,9 +193,10 @@ public final class OpenGroupPoller : NSObject {
let openGroup = self.openGroup
let _ = OpenGroupAPI.getDeletedMessageServerIDs(for: openGroup.channel, on: openGroup.server).done(on: DispatchQueue.global(qos: .default)) { deletedMessageServerIDs in
let deletedMessageIDs = deletedMessageServerIDs.compactMap { Storage.shared.getIDForMessage(withServerID: UInt64($0)) }
SNMessagingKitConfiguration.shared.storage.writeSync { transaction in
SNMessagingKitConfiguration.shared.storage.write { transaction in
deletedMessageIDs.forEach { messageID in
TSMessage.fetch(uniqueId: String(messageID))?.remove(with: transaction as! YapDatabaseReadWriteTransaction)
let transaction = transaction as! YapDatabaseReadWriteTransaction
TSMessage.fetch(uniqueId: messageID, transaction: transaction)?.remove(with: transaction)
}
}
}

@ -71,7 +71,8 @@ public protocol SessionMessagingKitStorageProtocol {
// MARK: - Open Group Metadata
func setUserCount(to newValue: Int, forOpenGroupWithID openGroupID: String, using transaction: Any)
func getIDForMessage(withServerID serverID: UInt64) -> UInt64?
func getIDForMessage(withServerID serverID: UInt64) -> String?
func setIDForMessage(withServerID serverID: UInt64, to messageID: String, using transaction: Any)
func setOpenGroupDisplayName(to displayName: String, for publicKey: String, inOpenGroupWithID openGroupID: String, using transaction: Any)
func setLastProfilePictureUploadDate(_ date: Date) // Stored in user defaults so no transaction is needed

Loading…
Cancel
Save