seekia/internal/moderation/reviewStorage/reviewStorage.go
2024-08-11 12:31:40 +00:00

1042 lines
39 KiB
Go

// 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
// -int64: Review creation time
// -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
// -int64: Review creation time
// -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
attributeProfileHashesList, _ := helpers.DeleteAllMatchingItemsFromList(attributeProfilesList, profileHash)
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
}