Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify hashing for avatar #22289

Merged
merged 8 commits into from
Jan 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions modules/avatar/hash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package avatar
lunny marked this conversation as resolved.
Show resolved Hide resolved

import (
"crypto/sha256"
"encoding/hex"
"strconv"
)

// HashAvatar will generate a unique string, which ensures that when there's a
// different unique ID while the data is the same, it will generate a different
// output. It will generate the output according to:
// HEX(HASH(uniqueID || - || data))
// The hash being used is SHA256.
// The sole purpose of the unique ID is to generate a distinct hash Such that
// two unique IDs with the same data will have a different hash output.
// The "-" byte is important to ensure that data cannot be modified such that
// the first byte is a number, which could lead to a "collision" with the hash
// of another unique ID.
func HashAvatar(uniqueID int64, data []byte) string {
h := sha256.New()
h.Write([]byte(strconv.FormatInt(uniqueID, 10)))
h.Write([]byte{'-'})
h.Write(data)
return hex.EncodeToString(h.Sum(nil))
}
3 changes: 1 addition & 2 deletions services/repository/avatar.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package repository

import (
"context"
"crypto/md5"
"fmt"
"image/png"
"io"
Expand All @@ -27,7 +26,7 @@ func UploadAvatar(repo *repo_model.Repository, data []byte) error {
return err
}

newAvatar := fmt.Sprintf("%d-%x", repo.ID, md5.Sum(data))
newAvatar := avatar.HashAvatar(repo.ID, data)
if repo.Avatar == newAvatar { // upload the same picture
return nil
}
Expand Down
5 changes: 2 additions & 3 deletions services/repository/avatar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ package repository

import (
"bytes"
"crypto/md5"
"fmt"
"image"
"image/png"
"testing"

repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/avatar"

"github.com/stretchr/testify/assert"
)
Expand All @@ -28,7 +27,7 @@ func TestUploadAvatar(t *testing.T) {

err := UploadAvatar(repo, buff.Bytes())
assert.NoError(t, err)
assert.Equal(t, fmt.Sprintf("%d-%x", 10, md5.Sum(buff.Bytes())), repo.Avatar)
assert.Equal(t, avatar.HashAvatar(10, buff.Bytes()), repo.Avatar)
}

func TestUploadBigAvatar(t *testing.T) {
Expand Down
7 changes: 1 addition & 6 deletions services/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package user

import (
"context"
"crypto/md5"
"fmt"
"image/png"
"io"
Expand Down Expand Up @@ -241,11 +240,7 @@ func UploadAvatar(u *user_model.User, data []byte) error {
defer committer.Close()

u.UseCustomAvatar = true
// Different users can upload same image as avatar
// If we prefix it with u.ID, it will be separated
// Otherwise, if any of the users delete his avatar
// Other users will lose their avatars too.
u.Avatar = fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", u.ID, md5.Sum(data)))))
u.Avatar = avatar.HashAvatar(u.ID, data)
if err = user_model.UpdateUserCols(ctx, u, "use_custom_avatar", "avatar"); err != nil {
return fmt.Errorf("updateUser: %w", err)
}
Expand Down