2024-04-11 15:51:56 +02:00
// reviewStorage provides functions to manage stored moderator reviews
// Reviews are created by moderators and determine the moderation verdict of messages, profiles, and identities.
package reviewStorage
import "seekia/internal/badgerDatabase"
import "seekia/internal/contentMetadata"
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/messaging/readMessages"
import "seekia/internal/moderation/readReviews"
import "seekia/internal/mySettings"
import "seekia/internal/network/appNetworkType/getAppNetworkType"
import "seekia/internal/network/backgroundDownloads"
import "seekia/internal/profiles/readProfiles"
import "bytes"
import "errors"
func GetNumberOfStoredReviews ( ) ( int64 , error ) {
numberOfReviews , err := badgerDatabase . GetNumberOfReviews ( )
if ( err != nil ) { return 0 , err }
return numberOfReviews , nil
}
//Outputs:
// -bool: Review is well formed
// -error
func AddReview ( newReview [ ] byte ) ( bool , error ) {
ableToRead , reviewHash , _ , _ , reviewerIdentityHash , _ , reviewType , reviewedHash , _ , _ , err := readReviews . ReadReviewAndHash ( true , newReview )
if ( err != nil ) { return false , err }
if ( ableToRead == false ) {
// Review is malformed, do nothing
// Host who sent review must be malicious.
return false , nil
}
exists , _ , err := badgerDatabase . GetReview ( reviewHash )
if ( err != nil ) { return false , err }
if ( exists == true ) {
// Review already imported, nothing to do.
return true , nil
}
err = badgerDatabase . AddReview ( reviewHash , newReview )
if ( err != nil ) { return false , err }
err = mySettings . SetSetting ( "ViewedContentNeedsRefreshYesNo" , "Yes" )
if ( err != nil ) { return false , err }
err = badgerDatabase . AddReviewerReviewHash ( reviewerIdentityHash , reviewHash )
if ( err != nil ) { return false , err }
if ( reviewType == "Identity" ) {
if ( len ( reviewedHash ) != 16 ) {
reviewedHashHex := encoding . EncodeBytesToHexString ( reviewedHash )
return false , errors . New ( "ReadReview returning invalid length reviewed Identity hash: " + reviewedHashHex )
}
reviewedIdentityHash := [ 16 ] byte ( reviewedHash )
err := badgerDatabase . AddIdentityReviewToList ( reviewedIdentityHash , reviewHash )
if ( err != nil ) { return false , err }
return true , nil
}
if ( reviewType == "Profile" ) {
if ( len ( reviewedHash ) != 28 ) {
reviewedHashHex := encoding . EncodeBytesToHexString ( reviewedHash )
return false , errors . New ( "ReadReview returning invalid length reviewed Profile hash: " + reviewedHashHex )
}
reviewedProfileHash := [ 28 ] byte ( reviewedHash )
err = badgerDatabase . AddProfileReviewToList ( reviewedProfileHash , reviewHash )
if ( err != nil ) { return false , err }
return true , nil
}
if ( reviewType == "Attribute" ) {
if ( len ( reviewedHash ) != 27 ) {
reviewedHashHex := encoding . EncodeBytesToHexString ( reviewedHash )
return false , errors . New ( "ReadReview returning invalid length reviewed Attribute hash: " + reviewedHashHex )
}
reviewedAttributeHash := [ 27 ] byte ( reviewedHash )
err = badgerDatabase . AddProfileAttributeReviewToList ( reviewedAttributeHash , reviewHash )
if ( err != nil ) { return false , err }
return true , nil
}
if ( reviewType == "Message" ) {
if ( len ( reviewedHash ) != 26 ) {
reviewedHashHex := encoding . EncodeBytesToHexString ( reviewedHash )
return false , errors . New ( "ReadReview returning invalid length reviewed Message hash: " + reviewedHashHex )
}
reviewedMessageHash := [ 26 ] byte ( reviewedHash )
err = badgerDatabase . AddMessageReviewToList ( reviewedMessageHash , reviewHash )
if ( err != nil ) { return false , err }
return true , nil
}
return false , errors . New ( "ReadReview returning invalid reviewType: " + reviewType )
}
//Outputs:
// -bool: Review exists
// -[]byte: Review bytes
// -map[string]string: Review map
// -error
func GetModeratorNewestIdentityReview ( moderatorIdentityHash [ 16 ] byte , reviewedIdentityHash [ 16 ] byte , networkType byte ) ( bool , [ ] byte , map [ string ] string , error ) {
isValid , err := identity . VerifyIdentityHash ( moderatorIdentityHash , true , "Moderator" )
if ( err != nil ) { return false , nil , nil , err }
if ( isValid == false ) {
moderatorIdentityHashHex := encoding . EncodeBytesToHexString ( moderatorIdentityHash [ : ] )
return false , nil , nil , errors . New ( "GetModeratorNewestIdentityReview called with invalid moderatorIdentityHash: " + moderatorIdentityHashHex )
}
isValid , err = identity . VerifyIdentityHash ( reviewedIdentityHash , false , "" )
if ( err != nil ) { return false , nil , nil , err }
if ( isValid == false ) {
reviewedIdentityHashHex := encoding . EncodeBytesToHexString ( reviewedIdentityHash [ : ] )
return false , nil , nil , errors . New ( "GetModeratorNewestIdentityReview called with invalid reviewedIdentityHash: " + reviewedIdentityHashHex )
}
isValid = helpers . VerifyNetworkType ( networkType )
if ( isValid == false ) {
networkTypeString := helpers . ConvertByteToString ( networkType )
return false , nil , nil , errors . New ( "GetModeratorNewestIdentityReview called with invalid networkType: " + networkTypeString )
}
anyReviewsExist , reviewHashesList , err := badgerDatabase . GetIdentityReviewsList ( reviewedIdentityHash )
if ( err != nil ) { return false , nil , nil , err }
if ( anyReviewsExist == false ) {
return false , nil , nil , nil
}
reviewsList := make ( [ ] readReviews . ReviewWithHash , 0 )
for _ , reviewHash := range reviewHashesList {
exists , reviewBytes , err := badgerDatabase . GetReview ( reviewHash )
if ( err != nil ) { return false , nil , nil , err }
if ( exists == false ) {
// Review has been deleted. This missing entry will be removed automatically.
continue
}
newReviewObject := readReviews . ReviewWithHash {
ReviewHash : reviewHash ,
ReviewBytes : reviewBytes ,
}
reviewsList = append ( reviewsList , newReviewObject )
}
verdictExists , reviewBytes , newestReviewMap , err := readReviews . GetModeratorNewestIdentityReviewFromReviewsList ( reviewsList , moderatorIdentityHash , reviewedIdentityHash , networkType )
if ( err != nil ) { return false , nil , nil , err }
if ( verdictExists == false ) {
return false , nil , nil , nil
}
return true , reviewBytes , newestReviewMap , nil
}
//Outputs:
// -bool: Profile verdict exists (At least 1 attribute ban, or a full profile approve/ban review exists)
// -string: Profile verdict "Ban"/"Approve" (incorporates full profile reviews and attribute bans)
// -bool: Full profile review exists
// -[]byte: Full profile review bytes
// -map[string]string: Full profile review map
// -map[[27]byte][]byte: Map of attribute approve reviews (Attribute hash -> Attribute review)
// -map[[27]byte][]byte: Map of attribute ban reviews (Attribute hash -> Attribute review)
// -error
func GetModeratorNewestProfileReviews ( moderatorIdentityHash [ 16 ] byte , profileHash [ 28 ] byte , profileNetworkType byte , profileAttributeHashesList [ ] [ 27 ] byte ) ( bool , string , bool , [ ] byte , map [ string ] string , map [ [ 27 ] byte ] [ ] byte , map [ [ 27 ] byte ] [ ] byte , error ) {
isValid := helpers . VerifyNetworkType ( profileNetworkType )
if ( isValid == false ) {
profileNetworkTypeString := helpers . ConvertByteToString ( profileNetworkType )
return false , "" , false , nil , nil , nil , nil , errors . New ( "GetModeratorNewestProfileReviews called with invalid profileNetworkType: " + profileNetworkTypeString )
}
fullProfileReviewExists , fullProfileReviewBytes , fullProfileReviewMap , fullProfileReviewVerdict , fullProfileReviewTime , err := getModeratorNewestFullProfileReview ( moderatorIdentityHash , profileHash , profileNetworkType )
if ( err != nil ) { return false , "" , false , nil , nil , nil , nil , err }
// Full profile approvals can be overwritten by an attribute ban of any attribute within the profile
// We check for attribute reviews
//Map Structure: Attribute hash -> Attribute review bytes
approvedAttributesMap := make ( map [ [ 27 ] byte ] [ ] byte )
//Map Structure: Attribute hash -> Attribute review bytes
bannedAttributesMap := make ( map [ [ 27 ] byte ] [ ] byte )
for _ , attributeHash := range profileAttributeHashesList {
attributeReviewExists , attributeReviewType , attributeReviewBytes , _ , attributeReviewVerdict , attributeReviewTime , err := GetModeratorNewestProfileAttributeReview ( moderatorIdentityHash , attributeHash , profileNetworkType , false )
if ( err != nil ) { return false , "" , false , nil , nil , nil , nil , err }
if ( attributeReviewExists == false ) {
continue
}
if ( attributeReviewType != "Attribute" ) {
return false , "" , false , nil , nil , nil , nil , errors . New ( "GetModeratorNewestProfileAttributeReview returning non-attribute review when integrateFullProfileApprovals == false" )
}
if ( attributeReviewVerdict == "Approve" ) {
// Attribute approve reviews do not change full profile ban reviews
approvedAttributesMap [ attributeHash ] = attributeReviewBytes
continue
}
if ( fullProfileReviewExists == true && fullProfileReviewVerdict == "Approve" ) {
if ( fullProfileReviewTime > attributeReviewTime ) {
// This attribute ban was created before the moderator approved the full profile
// It has therefore been overwritten
continue
}
}
bannedAttributesMap [ attributeHash ] = attributeReviewBytes
}
if ( len ( bannedAttributesMap ) > 0 ) {
// Profile is banned by moderator
if ( fullProfileReviewExists == true && fullProfileReviewVerdict == "Approve" ) {
// An attribute was banned, which overwrites the full profile review
return true , "Ban" , false , nil , nil , approvedAttributesMap , bannedAttributesMap , nil
}
return true , "Ban" , fullProfileReviewExists , fullProfileReviewBytes , fullProfileReviewMap , approvedAttributesMap , bannedAttributesMap , nil
}
if ( fullProfileReviewExists == false ) {
return false , "" , false , nil , nil , approvedAttributesMap , bannedAttributesMap , nil
}
return true , fullProfileReviewVerdict , true , fullProfileReviewBytes , fullProfileReviewMap , approvedAttributesMap , bannedAttributesMap , nil
}
// This function does not integrate the moderator's attribute reviews
//Outputs:
// -bool: Review exists
// -[]byte: Review bytes
// -map[string]string: Review map
// -string: Review verdict
2024-06-11 06:59:06 +02:00
// -int64: Review creation time
2024-04-11 15:51:56 +02:00
// -error
func getModeratorNewestFullProfileReview ( moderatorIdentityHash [ 16 ] byte , profileHash [ 28 ] byte , profileNetworkType byte ) ( bool , [ ] byte , map [ string ] string , string , int64 , error ) {
isValid , err := identity . VerifyIdentityHash ( moderatorIdentityHash , true , "Moderator" )
if ( err != nil ) { return false , nil , nil , "" , 0 , err }
if ( isValid == false ) {
moderatorIdentityHashHex := encoding . EncodeBytesToHexString ( moderatorIdentityHash [ : ] )
return false , nil , nil , "" , 0 , errors . New ( "getModeratorNewestFullProfileReview called with invalid moderatorIdentityHash: " + moderatorIdentityHashHex )
}
isValid , err = readProfiles . VerifyProfileHash ( profileHash , false , "" , true , false )
if ( err != nil ) { return false , nil , nil , "" , 0 , err }
if ( isValid == false ) {
profileHashHex := encoding . EncodeBytesToHexString ( profileHash [ : ] )
return false , nil , nil , "" , 0 , errors . New ( "getModeratorNewestFullProfileReview called with invalid profileHash: " + profileHashHex )
}
isValid = helpers . VerifyNetworkType ( profileNetworkType )
if ( isValid == false ) {
profileNetworkTypeString := helpers . ConvertByteToString ( profileNetworkType )
return false , nil , nil , "" , 0 , errors . New ( "getModeratorNewestFullProfileReview called with invalid profileNetworkType: " + profileNetworkTypeString )
}
anyReviewsExist , reviewHashesList , err := badgerDatabase . GetProfileReviewsList ( profileHash )
if ( err != nil ) { return false , nil , nil , "" , 0 , err }
if ( anyReviewsExist == false ) {
return false , nil , nil , "" , 0 , nil
}
reviewsList := make ( [ ] readReviews . ReviewWithHash , 0 )
for _ , reviewHash := range reviewHashesList {
exists , reviewBytes , err := badgerDatabase . GetReview ( reviewHash )
if ( err != nil ) { return false , nil , nil , "" , 0 , err }
if ( exists == false ) {
// Review has been deleted. This missing entry will be removed automatically.
continue
}
reviewObject := readReviews . ReviewWithHash {
ReviewHash : reviewHash ,
ReviewBytes : reviewBytes ,
}
reviewsList = append ( reviewsList , reviewObject )
}
reviewExists , reviewBytes , reviewMap , reviewVerdict , reviewTime , err := readReviews . GetModeratorNewestProfileReviewFromReviewsList ( reviewsList , moderatorIdentityHash , profileHash , profileNetworkType )
if ( err != nil ) { return false , nil , nil , "" , 0 , err }
if ( reviewExists == false ) {
return false , nil , nil , "" , 0 , nil
}
return true , reviewBytes , reviewMap , reviewVerdict , reviewTime , nil
}
//Outputs:
// -bool: Review exists
// -string: Review Type ("Profile"/"Attribute")
// -[]byte: Review bytes
// -map[string]string: Review map
// -string: Review verdict
// -int64: Time of review
// -error
func GetModeratorNewestProfileAttributeReview ( moderatorIdentityHash [ 16 ] byte , attributeHash [ 27 ] byte , attributeNetworkType byte , integrateFullProfileApprovals bool ) ( bool , string , [ ] byte , map [ string ] string , string , int64 , error ) {
isValid , err := identity . VerifyIdentityHash ( moderatorIdentityHash , true , "Moderator" )
if ( err != nil ) { return false , "" , nil , nil , "" , 0 , err }
if ( isValid == false ) {
moderatorIdentityHashHex := encoding . EncodeBytesToHexString ( moderatorIdentityHash [ : ] )
return false , "" , nil , nil , "" , 0 , errors . New ( "GetModeratorNewestProfileAttributeReview called with invalid moderatorIdentityHash: " + moderatorIdentityHashHex )
}
isValid , err = readProfiles . VerifyAttributeHash ( attributeHash , false , "" , false , false )
if ( err != nil ) { return false , "" , nil , nil , "" , 0 , err }
if ( isValid == false ) {
attributeHashHex := encoding . EncodeBytesToHexString ( attributeHash [ : ] )
return false , "" , nil , nil , "" , 0 , errors . New ( "GetModeratorNewestProfileAttributeReview called with invalid attributeHash: " + attributeHashHex )
}
isValid = helpers . VerifyNetworkType ( attributeNetworkType )
if ( isValid == false ) {
attributeNetworkTypeString := helpers . ConvertByteToString ( attributeNetworkType )
return false , "" , nil , nil , "" , 0 , errors . New ( "GetModeratorNewestProfileAttributeReview called with invalid attributeNetworkType: " + attributeNetworkTypeString )
}
//Outputs:
// -bool: Attribute review exists
// -[]byte: Review bytes
// -map[string]string: Review map
// -string: Review verdict
2024-06-11 06:59:06 +02:00
// -int64: Review creation time
2024-04-11 15:51:56 +02:00
// -error
getModeratorNewestAttributeReview := func ( ) ( bool , [ ] byte , map [ string ] string , string , int64 , error ) {
anyReviewsExist , reviewHashesList , err := badgerDatabase . GetProfileAttributeReviewsList ( attributeHash )
if ( err != nil ) { return false , nil , nil , "" , 0 , err }
if ( anyReviewsExist == false ) {
return false , nil , nil , "" , 0 , nil
}
reviewsList := make ( [ ] readReviews . ReviewWithHash , 0 )
for _ , reviewHash := range reviewHashesList {
exists , reviewBytes , err := badgerDatabase . GetReview ( reviewHash )
if ( err != nil ) { return false , nil , nil , "" , 0 , err }
if ( exists == false ) {
// Review has been deleted. This missing entry will be removed automatically.
continue
}
reviewObject := readReviews . ReviewWithHash {
ReviewHash : reviewHash ,
ReviewBytes : reviewBytes ,
}
reviewsList = append ( reviewsList , reviewObject )
}
attributeReviewExists , attributeReviewBytes , attributeReviewMap , attributeReviewVerdict , attributeReviewTime , err := readReviews . GetModeratorNewestProfileAttributeReviewFromReviewsList ( reviewsList , moderatorIdentityHash , attributeHash , attributeNetworkType )
if ( err != nil ) { return false , nil , nil , "" , 0 , err }
if ( attributeReviewExists == false ) {
return false , nil , nil , "" , 0 , nil
}
return true , attributeReviewBytes , attributeReviewMap , attributeReviewVerdict , attributeReviewTime , nil
}
attributeReviewExists , attributeReviewBytes , attributeReviewMap , attributeReviewVerdict , attributeReviewTime , err := getModeratorNewestAttributeReview ( )
if ( err != nil ) { return false , "" , nil , nil , "" , 0 , err }
if ( integrateFullProfileApprovals == false ) {
if ( attributeReviewExists == false ) {
return false , "" , nil , nil , "" , 0 , nil
}
return true , "Attribute" , attributeReviewBytes , attributeReviewMap , attributeReviewVerdict , attributeReviewTime , nil
}
if ( attributeReviewExists == true && attributeReviewVerdict == "Approve" ) {
// The user approved the attribute
// We don't have to check for full profile approvals, because they would not change this verdict
return true , "Attribute" , attributeReviewBytes , attributeReviewMap , "Approve" , attributeReviewTime , nil
}
// Now we check for full profile approvals
// A full profile approval is equivalent to an attribute approval for all attributes within the profile
// attributeProfileHashesList is a list of all profile hashes for profiles which contain the attribute
anyExist , attributeProfileHashesList , err := badgerDatabase . GetAttributeProfilesList ( attributeHash )
if ( err != nil ) { return false , "" , nil , nil , "" , 0 , err }
if ( anyExist == true ) {
for _ , profileHash := range attributeProfileHashesList {
// We are only checking for full profile approvals
// We don't need to check for profile attribute bans, because we are only concerned with the provided attribute's status
// A full profile approval approves all attributes.
// If a user bans a full profile, it does not change the status of any of their attribute approvals of the profile
fullProfileReviewExists , fullProfileReviewBytes , fullProfileReviewMap , fullProfileVerdict , fullProfileVerdictTime , err := getModeratorNewestFullProfileReview ( moderatorIdentityHash , profileHash , attributeNetworkType )
if ( err != nil ) { return false , "" , nil , nil , "" , 0 , err }
if ( fullProfileReviewExists == true && fullProfileVerdict == "Approve" ) {
if ( attributeReviewExists == true && fullProfileVerdictTime < attributeReviewTime ) {
// The moderator banned the attribute after approving the full profile
// The profile approval is therefore disregarded.
continue
}
// The moderator approved the full profile after banning the attribute, thus, the attribute is approved
return true , "Profile" , fullProfileReviewBytes , fullProfileReviewMap , "Approve" , fullProfileVerdictTime , nil
}
}
}
if ( attributeReviewExists == true ) {
// We could not find any full profile approvals that undo the attribute ban
return true , "Attribute" , attributeReviewBytes , attributeReviewMap , "Ban" , attributeReviewTime , nil
}
return false , "" , nil , nil , "" , 0 , nil
}
//Outputs:
// -bool: Review exists
// -[]byte: Review bytes
// -map[string]string: Review map
// -error
func GetModeratorNewestMessageReview ( moderatorIdentityHash [ 16 ] byte , messageHash [ 26 ] byte , messageNetworkType byte , messageCipherKey [ 25 ] byte ) ( bool , [ ] byte , map [ string ] string , error ) {
isValid , err := identity . VerifyIdentityHash ( moderatorIdentityHash , true , "Moderator" )
if ( err != nil ) { return false , nil , nil , err }
if ( isValid == false ) {
moderatorIdentityHashHex := encoding . EncodeBytesToHexString ( moderatorIdentityHash [ : ] )
return false , nil , nil , errors . New ( "GetModeratorNewestMessageReview called with invalid moderatorIdentityHash: " + moderatorIdentityHashHex )
}
isValid = helpers . VerifyNetworkType ( messageNetworkType )
if ( isValid == false ) {
messageNetworkTypeString := helpers . ConvertByteToString ( messageNetworkType )
return false , nil , nil , errors . New ( "GetModeratorNewestMessageReview called with invalid messageNetworkType: " + messageNetworkTypeString )
}
anyReviewsExist , reviewHashesList , err := badgerDatabase . GetMessageReviewsList ( messageHash )
if ( err != nil ) { return false , nil , nil , err }
if ( anyReviewsExist == false ) {
return false , nil , nil , nil
}
reviewsList := make ( [ ] readReviews . ReviewWithHash , 0 )
for _ , reviewHash := range reviewHashesList {
exists , reviewBytes , err := badgerDatabase . GetReview ( reviewHash )
if ( err != nil ) { return false , nil , nil , err }
if ( exists == false ) {
// Review has been deleted. This missing entry will be removed automatically.
continue
}
reviewObject := readReviews . ReviewWithHash {
ReviewHash : reviewHash ,
ReviewBytes : reviewBytes ,
}
reviewsList = append ( reviewsList , reviewObject )
}
reviewExists , reviewBytes , reviewMap , _ , err := readReviews . GetModeratorNewestMessageReviewFromReviewsList ( reviewsList , moderatorIdentityHash , messageHash , messageNetworkType , messageCipherKey )
if ( err != nil ) { return false , nil , nil , err }
if ( reviewExists == false ) {
return false , nil , nil , nil
}
return true , reviewBytes , reviewMap , nil
}
// This function returns all reviews by a provided moderator, omitting older reviews for the same reviewed hashes
// Will omit None reviews
//Outputs:
// -[][]byte: List of reviews
// -error
func GetAllNewestReviewsCreatedByModerator ( moderatorIdentityHash [ 16 ] byte , reviewType string , networkType byte ) ( [ ] [ ] byte , error ) {
isValid := helpers . VerifyNetworkType ( networkType )
if ( isValid == false ) {
networkTypeString := helpers . ConvertByteToString ( networkType )
return nil , errors . New ( "GetAllNewestReviewsCreatedByModerator called with invalid networkType: " + networkTypeString )
}
exists , reviewHashesList , err := badgerDatabase . GetReviewerReviewHashesList ( moderatorIdentityHash , reviewType )
if ( err != nil ) { return nil , err }
if ( exists == false ) {
emptyList := make ( [ ] [ ] byte , 0 )
return emptyList , nil
}
reviewsList := make ( [ ] readReviews . ReviewWithHash , 0 )
for _ , reviewHash := range reviewHashesList {
exists , reviewBytes , err := badgerDatabase . GetReview ( reviewHash )
if ( err != nil ) { return nil , err }
if ( exists == false ) {
// Review has been deleted. The missing entry will be removed automatically in the background.
continue
}
reviewObject := readReviews . ReviewWithHash {
ReviewHash : reviewHash ,
ReviewBytes : reviewBytes ,
}
reviewsList = append ( reviewsList , reviewObject )
}
reviewsMap , err := readReviews . GetNewestModeratorReviewsMapFromReviewsList ( reviewsList , moderatorIdentityHash , networkType , true , reviewType )
if ( err != nil ) { return nil , err }
newestReviewsList := helpers . GetListOfMapValues ( reviewsMap )
return newestReviewsList , nil
}
// This function returns the number of ban advocates for a user
// This returns all advocates, banned or not
//Outputs:
// -bool: Downloading required reviews and profiles
// -int: Number of ban advocates
// -error
func GetNumberOfBanAdvocatesForIdentity ( identityHash [ 16 ] byte , networkType byte ) ( bool , int , error ) {
isValid := helpers . VerifyNetworkType ( networkType )
if ( isValid == false ) {
networkTypeString := helpers . ConvertByteToString ( networkType )
return false , 0 , errors . New ( "GetNumberOfBanAdvocatesForIdentity called with invalid networkType: " + networkTypeString )
}
appNetworkType , err := getAppNetworkType . GetAppNetworkType ( )
if ( err != nil ) { return false , 0 , err }
if ( appNetworkType != networkType ) {
// We are not downloading reviews for this network type.
return false , 0 , nil
}
downloadingRequiredReviews , err := backgroundDownloads . CheckIfAppCanDetermineIdentityVerdicts ( identityHash )
if ( err != nil ) { return false , 0 , err }
if ( downloadingRequiredReviews == false ) {
return false , 0 , nil
}
banAdvocatesMap , err := GetIdentityBanAdvocatesMap ( identityHash , networkType )
if ( err != nil ) { return false , 0 , err }
numberOfBanAdvocates := len ( banAdvocatesMap )
return true , numberOfBanAdvocates , nil
}
//Outputs:
// -bool: Message metadata is known
// -bool: Downloading required reviews and profiles
// -int: Number of reviewers
// -error
func GetNumberOfMessageReviewers ( messageHash [ 26 ] byte ) ( bool , bool , int , error ) {
metadataExists , _ , messageNetworkType , _ , messageInbox , messageCipherKeyHash , err := contentMetadata . GetMessageMetadata ( messageHash )
if ( err != nil ) { return false , false , 0 , err }
if ( metadataExists == false ) {
return false , false , 0 , nil
}
downloadingRequiredData , err := backgroundDownloads . CheckIfAppCanDetermineMessageVerdict ( messageNetworkType , messageInbox , true , messageHash )
if ( err != nil ) { return false , false , 0 , err }
if ( downloadingRequiredData == false ) {
return true , false , 0 , nil
}
approveAdvocatesMap , banAdvocatesMap , err := GetMessageVerdictMaps ( messageHash , messageNetworkType , messageCipherKeyHash )
if ( err != nil ) { return false , false , 0 , err }
numberOfReviewers := len ( approveAdvocatesMap ) + len ( banAdvocatesMap )
return true , true , numberOfReviewers , nil
}
// This function will integrate attribute bans
//Outputs:
// -bool: Profile metadata is known
// -bool: Downloading required reviews and profiles
// -int: Number of reviewers
// -error
func GetNumberOfProfileReviewers ( profileHash [ 28 ] byte ) ( bool , bool , int , error ) {
metadataExists , _ , profileNetworkType , profileAuthor , _ , profileIsDisabled , _ , profileAttributeHashesMap , err := contentMetadata . GetProfileMetadata ( profileHash )
if ( err != nil ) { return false , false , 0 , err }
if ( metadataExists == false ) {
return false , false , 0 , nil
}
if ( profileIsDisabled == true ) {
// Disabled profiles cannot be reviewed
return true , true , 0 , nil
}
appNetworkType , err := getAppNetworkType . GetAppNetworkType ( )
if ( err != nil ) { return false , false , 0 , err }
if ( appNetworkType != profileNetworkType ) {
// We are not downloading reviews for this network type.
return true , false , 0 , nil
}
downloadingRequiredReviews , err := backgroundDownloads . CheckIfAppCanDetermineIdentityVerdicts ( profileAuthor )
if ( err != nil ) { return false , false , 0 , err }
if ( downloadingRequiredReviews == false ) {
return true , false , 0 , nil
}
// profileAttributeHashesMap is a map whose values are the attribute hashes of the profile
profileAttributeHashesList := helpers . GetListOfMapValues ( profileAttributeHashesMap )
approveAdvocatesMap , banAdvocatesMap , err := GetProfileVerdictMaps ( profileHash , profileNetworkType , true , profileAttributeHashesList )
if ( err != nil ) { return false , false , 0 , err }
numberOfReviewers := len ( approveAdvocatesMap ) + len ( banAdvocatesMap )
return true , true , numberOfReviewers , nil
}
// This function will go through all reviews for an identity and return the ban advocates
//Outputs:
// -map[[16]byte]int64: Ban advocate identity hash -> Time of ban
// -error
func GetIdentityBanAdvocatesMap ( identityHash [ 16 ] byte , networkType byte ) ( map [ [ 16 ] byte ] int64 , error ) {
isValid := helpers . VerifyNetworkType ( networkType )
if ( isValid == false ) {
networkTypeString := helpers . ConvertByteToString ( networkType )
return nil , errors . New ( "GetIdentityBanAdvocatesMap called with invalid networkType: " + networkTypeString )
}
exists , reviewHashesList , err := badgerDatabase . GetIdentityReviewsList ( identityHash )
if ( err != nil ) { return nil , err }
if ( exists == false ) {
emptyMap := make ( map [ [ 16 ] byte ] int64 )
return emptyMap , nil
}
reviewsList := make ( [ ] readReviews . ReviewWithHash , 0 )
for _ , reviewHash := range reviewHashesList {
exists , reviewBytes , err := badgerDatabase . GetReview ( reviewHash )
if ( err != nil ) { return nil , err }
if ( exists == false ) {
// Review must have been deleted. The missing review entry will be pruned automatically
continue
}
reviewObject := readReviews . ReviewWithHash {
ReviewHash : reviewHash ,
ReviewBytes : reviewBytes ,
}
reviewsList = append ( reviewsList , reviewObject )
}
banAdvocatesMap , err := readReviews . GetIdentityBanAdvocatesMapFromReviewsList ( reviewsList , identityHash , networkType )
if ( err != nil ) { return nil , err }
return banAdvocatesMap , nil
}
// This function will go through all reviews for a profile and return the approve and ban advocates
// If integrateAttributeBans == false, we will only check for full profile approvals/bans
//Outputs:
// -map[[16]byte]int64: Approve advocates map (Moderator identity hash -> Time of review)
// -map[[16]byte]int64: Ban advocates map (Moderator identity hash -> Time of review)
// -error
func GetProfileVerdictMaps ( profileHash [ 28 ] byte , profileNetworkType byte , integrateAttributeBans bool , attributeHashesList [ ] [ 27 ] byte ) ( map [ [ 16 ] byte ] int64 , map [ [ 16 ] byte ] int64 , error ) {
isValid := helpers . VerifyNetworkType ( profileNetworkType )
if ( isValid == false ) {
profileNetworkTypeString := helpers . ConvertByteToString ( profileNetworkType )
return nil , nil , errors . New ( "GetProfileVerdictMaps called with invalid profileNetworkType: " + profileNetworkTypeString )
}
exists , reviewHashesList , err := badgerDatabase . GetProfileReviewsList ( profileHash )
if ( err != nil ) { return nil , nil , err }
if ( exists == false ) {
emptyMapA := make ( map [ [ 16 ] byte ] int64 )
emptyMapB := make ( map [ [ 16 ] byte ] int64 )
return emptyMapA , emptyMapB , nil
}
reviewsList := make ( [ ] readReviews . ReviewWithHash , 0 )
for _ , reviewHash := range reviewHashesList {
exists , reviewBytes , err := badgerDatabase . GetReview ( reviewHash )
if ( err != nil ) { return nil , nil , err }
if ( exists == false ) {
// Review has been deleted. The missing review entry will be pruned automatically.
continue
}
reviewObject := readReviews . ReviewWithHash {
ReviewHash : reviewHash ,
ReviewBytes : reviewBytes ,
}
reviewsList = append ( reviewsList , reviewObject )
}
approveAdvocatesMap , banAdvocatesMap , err := readReviews . GetProfileVerdictMapsFromReviewsList ( reviewsList , profileHash , profileNetworkType )
if ( err != nil ) { return nil , nil , err }
if ( integrateAttributeBans == false ) {
return approveAdvocatesMap , banAdvocatesMap , nil
}
// Now we check for attribute ban advocates
// A ban of any attribute within the profile is equivalent to a profile ban
// attributeHashesList is a list of all attribute hashes within the provided profile
integrateProfileAttributeBans := func ( ) error {
for _ , attributeHash := range attributeHashesList {
// We have to check for full profile reviews of all profiles by the user that contain this attribute
// Any of these full profile approvals are equivalent to approving all profile attributes
getAttributeProfileHashesList := func ( ) ( [ ] [ 28 ] byte , error ) {
exists , attributeProfilesList , err := badgerDatabase . GetAttributeProfilesList ( attributeHash )
if ( err != nil ) { return nil , err }
if ( exists == false ) {
emptyList := make ( [ ] [ 28 ] byte , 0 )
return emptyList , nil
}
// We can omit the current profileHash, because we are already checking it
2024-08-11 14:31:40 +02:00
attributeProfileHashesList , _ := helpers . DeleteAllMatchingItemsFromList ( attributeProfilesList , profileHash )
2024-04-11 15:51:56 +02:00
return attributeProfileHashesList , nil
}
attributeProfileHashesList , err := getAttributeProfileHashesList ( )
if ( err != nil ) { return err }
_ , attributeBanAdvocatesMap , err := GetProfileAttributeVerdictMaps ( attributeHash , profileNetworkType , true , attributeProfileHashesList )
if ( err != nil ) { return err }
for moderatorIdentityHash , attributeBanTime := range attributeBanAdvocatesMap {
existingApproveTime , exists := approveAdvocatesMap [ moderatorIdentityHash ]
if ( exists == true ) {
if ( existingApproveTime < attributeBanTime ) {
// The moderator banned a profile attribute after approving the profile
delete ( approveAdvocatesMap , moderatorIdentityHash )
banAdvocatesMap [ moderatorIdentityHash ] = attributeBanTime
// We don't have to check for any more attribute bans
// They can only cause an full profile approval to change to a ban
return nil
}
}
}
}
return nil
}
err = integrateProfileAttributeBans ( )
if ( err != nil ) { return nil , nil , err }
return approveAdvocatesMap , banAdvocatesMap , nil
}
// This function will go through all reviews for a profile attribute and return the approve and ban advocates
// This also finds full profile approve reviews and treats them as approvals for all attributes within the profile
//Outputs:
// -map[[16]byte]int64: Approve advocates map (Moderator identity hash -> Time of approval)
// -map[[16]byte]int64: Ban advocates map (Moderator identity hash -> Time of ban)
// -error
func GetProfileAttributeVerdictMaps ( attributeHash [ 27 ] byte , attributeNetworkType byte , integrateFullProfileApprovals bool , attributeProfileHashesList [ ] [ 28 ] byte ) ( map [ [ 16 ] byte ] int64 , map [ [ 16 ] byte ] int64 , error ) {
isValid := helpers . VerifyNetworkType ( attributeNetworkType )
if ( isValid == false ) {
attributeNetworkTypeString := helpers . ConvertByteToString ( attributeNetworkType )
return nil , nil , errors . New ( "GetProfileAttributeVerdictMaps called with invalid attributeNetworkType: " + attributeNetworkTypeString )
}
exists , reviewHashesList , err := badgerDatabase . GetProfileAttributeReviewsList ( attributeHash )
if ( err != nil ) { return nil , nil , err }
if ( exists == false ) {
emptyMapA := make ( map [ [ 16 ] byte ] int64 )
emptyMapB := make ( map [ [ 16 ] byte ] int64 )
return emptyMapA , emptyMapB , nil
}
reviewsList := make ( [ ] readReviews . ReviewWithHash , 0 )
for _ , reviewHash := range reviewHashesList {
exists , reviewBytes , err := badgerDatabase . GetReview ( reviewHash )
if ( err != nil ) { return nil , nil , err }
if ( exists == false ) {
// Review has been deleted. The missing review entry will be pruned automatically
continue
}
reviewObject := readReviews . ReviewWithHash {
ReviewHash : reviewHash ,
ReviewBytes : reviewBytes ,
}
reviewsList = append ( reviewsList , reviewObject )
}
approveAdvocatesMap , banAdvocatesMap , err := readReviews . GetProfileAttributeVerdictMapsFromReviewsList ( reviewsList , attributeHash , attributeNetworkType )
if ( err != nil ) { return nil , nil , err }
if ( integrateFullProfileApprovals == false ) {
return approveAdvocatesMap , banAdvocatesMap , nil
}
// Now we check for full profile approve advocates
// A full profile approval is equivalent to an attribute approval for all attributes within the profile
// attributeProfileHashesList is a list of all profile hashes for profiles which contain the attribute
integrateProfileApprovals := func ( ) error {
for _ , profileHash := range attributeProfileHashesList {
// We are only checking for full profile approvals
// We don't need to check for profile attribute bans, because we are only concerned with the provided attribute's status
// A full profile approval approves all attributes.
// If the user bans a profile's attribute after approving the full profile, the other attributes are still considered approved by the user
fullProfileApproveAdvocatesMap , _ , err := GetProfileVerdictMaps ( profileHash , attributeNetworkType , false , nil )
if ( err != nil ) { return err }
for moderatorIdentityHash , fullProfileApproveTime := range fullProfileApproveAdvocatesMap {
existingBanTime , exists := banAdvocatesMap [ moderatorIdentityHash ]
if ( exists == true ) {
if ( existingBanTime < fullProfileApproveTime ) {
// The moderator approved the full profile after banning the attribute
delete ( banAdvocatesMap , moderatorIdentityHash )
approveAdvocatesMap [ moderatorIdentityHash ] = fullProfileApproveTime
// We don't have to check for any more full profile approvals
// They can only cause an attribute ban to change to an approval
return nil
}
}
}
}
return nil
}
err = integrateProfileApprovals ( )
if ( err != nil ) { return nil , nil , err }
return approveAdvocatesMap , banAdvocatesMap , nil
}
// This function will go through all reviews for a message and return the approve and ban advocates
// We use the provided message CipherKeyHash to ensure the reviewers have seen the message
//Outputs:
// -map[[16]byte]int64: Approve advocate identity hash -> Time of approval
// -map[[16]byte]int64: Ban advocate identity hash -> Time of ban
// -error
func GetMessageVerdictMaps ( messageHash [ 26 ] byte , messageNetworkType byte , messageCipherKeyHash [ 25 ] byte ) ( map [ [ 16 ] byte ] int64 , map [ [ 16 ] byte ] int64 , error ) {
isValid := helpers . VerifyNetworkType ( messageNetworkType )
if ( isValid == false ) {
messageNetworkTypeString := helpers . ConvertByteToString ( messageNetworkType )
return nil , nil , errors . New ( "GetMessageVerdictMaps called with invalid messageNetworkType: " + messageNetworkTypeString )
}
exists , reviewHashesList , err := badgerDatabase . GetMessageReviewsList ( messageHash )
if ( err != nil ) { return nil , nil , err }
if ( exists == false ) {
// There are no reviews for this message
emptyMapA := make ( map [ [ 16 ] byte ] int64 )
emptyMapB := make ( map [ [ 16 ] byte ] int64 )
return emptyMapA , emptyMapB , nil
}
reviewsList := make ( [ ] readReviews . ReviewWithHash , 0 )
for _ , reviewHash := range reviewHashesList {
exists , reviewBytes , err := badgerDatabase . GetReview ( reviewHash )
if ( err != nil ) { return nil , nil , err }
if ( exists == false ) {
// Review has been deleted. Missing review entry will be pruned automatically
continue
}
reviewObject := readReviews . ReviewWithHash {
ReviewHash : reviewHash ,
ReviewBytes : reviewBytes ,
}
reviewsList = append ( reviewsList , reviewObject )
}
approveAdvocatesMap , banAdvocatesMap , err := readReviews . GetMessageVerdictMapsFromReviewsList ( reviewsList , messageHash , messageNetworkType , messageCipherKeyHash )
if ( err != nil ) { return nil , nil , err }
return approveAdvocatesMap , banAdvocatesMap , nil
}
// We use this function to find a valid message cipher key to decrypt a message for moderation
// We use the message's CipherKeyHash to verify that the author of the review has seen the decrypted message
// If the cipher key does not decrypt the message, then we know the author of the review and the message are malicious
//Outputs:
// -bool: Message cipher key found
// -[32]byte: Message Cipher key
// -error
func GetMessageCipherKeyFromAnyReview ( messageHash [ 26 ] byte , messageNetworkType byte , messageCipherKeyHash [ 25 ] byte ) ( bool , [ 32 ] byte , error ) {
isValid := helpers . VerifyNetworkType ( messageNetworkType )
if ( isValid == false ) {
messageNetworkTypeString := helpers . ConvertByteToString ( messageNetworkType )
return false , [ 32 ] byte { } , errors . New ( "GetMessageCipherKeyFromAnyReview called with invalid messageNetworkType: " + messageNetworkTypeString )
}
exists , reviewHashesList , err := badgerDatabase . GetMessageReviewsList ( messageHash )
if ( err != nil ) { return false , [ 32 ] byte { } , err }
if ( exists == false ) {
return false , [ 32 ] byte { } , nil
}
if ( len ( reviewHashesList ) == 0 ) {
return false , [ 32 ] byte { } , nil
}
for _ , reviewHash := range reviewHashesList {
exists , reviewBytes , err := badgerDatabase . GetReview ( reviewHash )
if ( err != nil ) { return false , [ 32 ] byte { } , err }
if ( exists == false ) {
// Message must have been deleted.
// The missing reviewsList entry will be removed automatically.
continue
}
ableToRead , _ , reviewNetworkType , _ , _ , reviewType , reviewedHash , _ , reviewMap , err := readReviews . ReadReview ( false , reviewBytes )
if ( err != nil ) { return false , [ 32 ] byte { } , err }
if ( ableToRead == false ) {
return false , [ 32 ] byte { } , errors . New ( "Database corrupt: Contains invalid review." )
}
if ( reviewNetworkType != messageNetworkType ) {
// The author of this review is malicious
// We will not try to get the message cipher key from the review, even if it exists
// This is because we want all moderators to see the same messages to review, regardless of if they were previously on a different network.
continue
}
if ( reviewType != "Message" ) {
return false , [ 32 ] byte { } , errors . New ( "Corrupt database: ReviewType does not match reviews list." )
}
areEqual := bytes . Equal ( reviewedHash , messageHash [ : ] )
if ( areEqual == false ) {
return false , [ 32 ] byte { } , errors . New ( "Corrupt database: Reviewed message hash does not match reviews list." )
}
messageCipherKeyString , exists := reviewMap [ "MessageCipherKey" ]
if ( exists == false ) {
return false , [ 32 ] byte { } , errors . New ( "Corrupt database: Contains review missing MessageCipherKey" )
}
messageCipherKey , err := readMessages . ReadMessageCipherKeyHex ( messageCipherKeyString )
if ( err != nil ) {
return false , [ 32 ] byte { } , errors . New ( "Corrupt database: Contains review with invalid MessageCipherKey: " + messageCipherKeyString + ". Reason: " + err . Error ( ) )
}
currentMessageCipherKeyHash , err := readMessages . ConvertMessageCipherKeyToCipherKeyHash ( messageCipherKey )
if ( err != nil ) { return false , [ 32 ] byte { } , err }
if ( currentMessageCipherKeyHash != messageCipherKeyHash ) {
// Reviewer is malicious.
continue
}
return true , messageCipherKey , nil
}
// No valid reviews found for message
return false , [ 32 ] byte { } , nil
}