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

Hash App token #6724

Merged
merged 34 commits into from
May 4, 2019
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
251d258
hashTokens
techknowlogick Apr 19, 2019
0ef8631
add to vendor
techknowlogick Apr 19, 2019
743fdad
sdk changes
techknowlogick Apr 19, 2019
892afcd
make lint
techknowlogick Apr 19, 2019
4367118
Merge branch 'master' into fix-3789
techknowlogick Apr 23, 2019
6e52eaa
Update token.go
techknowlogick Apr 23, 2019
563c24f
Update token.go
techknowlogick Apr 23, 2019
2eac502
update gomod
techknowlogick Apr 23, 2019
1111139
make fmt
techknowlogick Apr 23, 2019
2f246a8
Merge branch 'master' into fix-3789
techknowlogick Apr 23, 2019
7156e8b
salt tokens
techknowlogick Apr 23, 2019
ae2ef47
make fmt
techknowlogick Apr 23, 2019
6acf155
generate swagger
techknowlogick Apr 23, 2019
a394b34
empty last line in swagger
techknowlogick Apr 23, 2019
883e6e6
make vendor
techknowlogick Apr 23, 2019
bddd6df
typo
techknowlogick Apr 23, 2019
42db74a
update error message
techknowlogick Apr 23, 2019
8884d41
update tests
techknowlogick Apr 23, 2019
3801b79
fix fixtures
techknowlogick Apr 23, 2019
7856fbd
add log to migration
techknowlogick Apr 23, 2019
1c7d93c
use appropriate variable name
techknowlogick Apr 23, 2019
04109a1
update test
techknowlogick Apr 23, 2019
8632e57
add sliceError protections (slice bounds out of range)
techknowlogick Apr 23, 2019
9002fd6
Update token.go
techknowlogick Apr 24, 2019
894d260
Merge branch 'master' into fix-3789
techknowlogick Apr 24, 2019
90f97d1
Merge branch 'master' into fix-3789
techknowlogick Apr 24, 2019
fde3cb8
Merge branch 'master' into fix-3789
lunny Apr 27, 2019
c428544
update year
techknowlogick Apr 27, 2019
a39f7d0
update per changes to v78
techknowlogick Apr 27, 2019
e8fa8bc
Merge branch 'master' into fix-3789
techknowlogick May 2, 2019
e1b765f
updpate per v78 migration changes
techknowlogick May 2, 2019
b1c097f
Merge branch 'master' into fix-3789
techknowlogick May 3, 2019
2b49738
Merge branch 'master' into fix-3789
techknowlogick May 4, 2019
ed40913
Merge branch 'master' into fix-3789
techknowlogick May 4, 2019
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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module code.gitea.io/gitea
go 1.12

require (
code.gitea.io/sdk v0.0.0-20190416172854-7d954d775498
code.gitea.io/sdk v0.0.0-20190419065346-2858b80da5f7
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/PuerkitoBio/goquery v0.0.0-20170324135448-ed7d758e9a34
github.com/RoaringBitmap/roaring v0.4.7 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cloud.google.com/go v0.30.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
code.gitea.io/sdk v0.0.0-20190416172854-7d954d775498 h1:rcjwXMYIjYts88akPiyy/GB+imecpf159jojChciEEw=
code.gitea.io/sdk v0.0.0-20190416172854-7d954d775498/go.mod h1:5bZt0dRznpn2JysytQnV0yCru3FwDv9O5G91jo+lDAk=
code.gitea.io/sdk v0.0.0-20190419065346-2858b80da5f7 h1:YggbbCVgggcOjKYmcB2wVOsEtJHgHUNFFJZDB6QcYTg=
code.gitea.io/sdk v0.0.0-20190419065346-2858b80da5f7/go.mod h1:5bZt0dRznpn2JysytQnV0yCru3FwDv9O5G91jo+lDAk=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/PuerkitoBio/goquery v0.0.0-20170324135448-ed7d758e9a34 h1:UsHpWO0Elp6NaWVARdZHjiYwkhrspHVEGsyIKPb9OI8=
Expand Down
8 changes: 4 additions & 4 deletions integrations/api_token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ func TestAPICreateAndDeleteToken(t *testing.T) {
var newAccessToken api.AccessToken
DecodeJSON(t, resp, &newAccessToken)
models.AssertExistsAndLoadBean(t, &models.AccessToken{
ID: newAccessToken.ID,
Name: newAccessToken.Name,
Sha1: newAccessToken.Sha1,
UID: user.ID,
ID: newAccessToken.ID,
Name: newAccessToken.Name,
Token: newAccessToken.Token,
UID: user.ID,
})

req = NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", newAccessToken.ID)
Expand Down
4 changes: 2 additions & 2 deletions models/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ func (err ErrDeployKeyNameAlreadyUsed) Error() string {

// ErrAccessTokenNotExist represents a "AccessTokenNotExist" kind of error.
type ErrAccessTokenNotExist struct {
SHA string
Token string
}

// IsErrAccessTokenNotExist checks if an error is a ErrAccessTokenNotExist.
Expand All @@ -517,7 +517,7 @@ func IsErrAccessTokenNotExist(err error) bool {
}

func (err ErrAccessTokenNotExist) Error() string {
return fmt.Sprintf("access token does not exist [sha: %s]", err.SHA)
return fmt.Sprintf("access token does not exist [sha: %s]", err.Token)
}

// ErrAccessTokenEmpty represents a "AccessTokenEmpty" kind of error.
Expand Down
16 changes: 13 additions & 3 deletions models/fixtures/access_token.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,32 @@
id: 1
uid: 1
name: Token A
sha1: hash1
#token: d2c6c1ba3890b309189a8e618c72a162e4efbf36
token_hash: 2b3668e11cb82d3af8c6e4524fc7841297668f5008d1626f0ad3417e9fa39af84c268248b78c481daa7e5dc437784003494f
token_salt: QuSiZr1byZ
token_last_eight: e4efbf36
created_unix: 946687980
updated_unix: 946687980

-
id: 2
uid: 1
name: Token B
sha1: hash2
#token: 4c6f36e6cf498e2a448662f915d932c09c5a146c
token_hash: 1a0e32a231ebbd582dc626c1543a42d3c63d4fa76c07c72862721467c55e8f81c923d60700f0528b5f5f443f055559d3a279
token_salt: Lfwopukrq5
token_last_eight: 9c5a146c
created_unix: 946687980
updated_unix: 946687980

-
id: 3
uid: 2
name: Token A
sha1: hash3
#token: 90a18faa671dc43924b795806ffe4fd169d28c91
token_hash: d6d404048048812d9e911d93aefbe94fc768d4876fdf75e3bef0bdc67828e0af422846d3056f2f25ec35c51dc92075685ec5
token_salt: 99ArgXKlQQ
token_last_eight: 69d28c91
created_unix: 946687980
updated_unix: 946687980
#commented out tokens so you can see what they are in plaintext
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ var migrations = []Migration{
NewMigration("add uploader id for table attachment", addUploaderIDForAttachment),
// v84 -> v85
NewMigration("add table to store original imported gpg keys", addGPGKeyImport),
// v85 -> v86
NewMigration("hash application token", hashAppToken),
}

// Migrate database to current version
Expand Down
135 changes: 135 additions & 0 deletions models/migrations/v85.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package migrations

import (
"fmt"

"github.com/go-xorm/core"
"github.com/go-xorm/xorm"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
)

func hashAppToken(x *xorm.Engine) error {
// AccessToken see models/token.go
type AccessToken struct {
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"INDEX"`
Name string
Sha1 string
Token string `xorm:"-"`
TokenHash string `xorm:"UNIQUE"` // sha256 of token
TokenSalt string
TokenLastEight string `xorm:"token_last_eight"`

CreatedUnix util.TimeStamp `xorm:"INDEX created"`
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
HasRecentActivity bool `xorm:"-"`
HasUsed bool `xorm:"-"`
}

// First remove the index
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}

var err error
if models.DbCfg.Type == core.POSTGRES || models.DbCfg.Type == core.SQLITE {
_, err = sess.Exec("DROP INDEX IF EXISTS UQE_access_token_sha1")
} else if models.DbCfg.Type == core.MSSQL {
_, err = sess.Exec(`DECLARE @ConstraintName VARCHAR(256)
DECLARE @SQL NVARCHAR(256)
SELECT @ConstraintName = obj.name FROM sys.columns col LEFT OUTER JOIN sys.objects obj ON obj.object_id = col.default_object_id AND obj.type = 'D' WHERE col.object_id = OBJECT_ID('access_token') AND obj.name IS NOT NULL AND col.name = 'sha1'
SET @SQL = N'ALTER TABLE [access_token] DROP CONSTRAINT [' + @ConstraintName + N']'
EXEC sp_executesql @SQL`)
} else if models.DbCfg.Type == core.MYSQL {
indexes, err := sess.QueryString(`SHOW INDEX FROM access_token WHERE KEY_NAME = 'UQE_access_token_sha1'`)
if err != nil {
return err
}

if len(indexes) >= 1 {
_, err = sess.Exec("DROP INDEX UQE_access_token_sha1 ON access_token")
}
} else {
_, err = sess.Exec("DROP INDEX UQE_access_token_sha1 ON access_token")
}
if err != nil {
return fmt.Errorf("Drop index failed: %v", err)
}

if err = sess.Commit(); err != nil {
return err
}

if err := sess.Begin(); err != nil {
return err
}

if err := x.Sync2(new(AccessToken)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}

if err = sess.Commit(); err != nil {
return err
}

if err := sess.Begin(); err != nil {
return err
}

// transform all tokens to hashes
const batchSize = 100
for start := 0; ; start += batchSize {
tokens := make([]*AccessToken, 0, batchSize)
if err := sess.Limit(batchSize, start).Find(&tokens); err != nil {
return err
}
if len(tokens) == 0 {
break
}

for _, token := range tokens {
// generate salt
salt, err := generate.GetRandomString(10)
if err != nil {
return err
}
token.TokenSalt = salt
token.TokenHash = hashToken(token.Sha1, salt)
if len(token.Sha1) < 8 {
log.Warn("Unable to transform token %s with name %s belonging to user ID %d, skipping transformation", token.Sha1, token.Name, token.UID)
continue
}
token.TokenLastEight = token.Sha1[len(token.Sha1)-8:]
token.Sha1 = "" // ensure to blank out column in case drop column doesn't work

if _, err := sess.ID(token.ID).Cols("token_hash, token_salt, token_last_eight, sha1").Update(token); err != nil {
return fmt.Errorf("couldn't add in sha1, token_hash, token_salt and token_last_eight: %v", err)
}

}
}

// Commit and begin new transaction for dropping columns
if err := sess.Commit(); err != nil {
return err
}
if err := sess.Begin(); err != nil {
return err
}

if err := dropTableColumns(sess, "access_token", "sha1"); err != nil {
return err
}
return sess.Commit()

}
51 changes: 37 additions & 14 deletions models/token.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package models

import (
"crypto/subtle"
"time"

gouuid "github.com/satori/go.uuid"

"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/util"
)

// AccessToken represents a personal access token.
type AccessToken struct {
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"INDEX"`
Name string
Sha1 string `xorm:"UNIQUE VARCHAR(40)"`
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"INDEX"`
Name string
Token string `xorm:"-"`
TokenHash string `xorm:"UNIQUE"` // sha256 of token
TokenSalt string
TokenLastEight string `xorm:"token_last_eight"`

CreatedUnix util.TimeStamp `xorm:"INDEX created"`
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
Expand All @@ -34,24 +40,41 @@ func (t *AccessToken) AfterLoad() {

// NewAccessToken creates new access token.
func NewAccessToken(t *AccessToken) error {
t.Sha1 = base.EncodeSha1(gouuid.NewV4().String())
_, err := x.Insert(t)
salt, err := generate.GetRandomString(10)
if err != nil {
return err
}
t.TokenSalt = salt
t.Token = base.EncodeSha1(gouuid.NewV4().String())
t.TokenHash = hashToken(t.Token, t.TokenSalt)
t.TokenLastEight = t.Token[len(t.Token)-8:]
_, err = x.Insert(t)
return err
}

// GetAccessTokenBySHA returns access token by given sha1.
func GetAccessTokenBySHA(sha string) (*AccessToken, error) {
if sha == "" {
// GetAccessTokenBySHA returns access token by given token value
func GetAccessTokenBySHA(token string) (*AccessToken, error) {
if token == "" {
return nil, ErrAccessTokenEmpty{}
}
t := &AccessToken{Sha1: sha}
has, err := x.Get(t)
if len(token) < 8 {
return nil, ErrAccessTokenNotExist{token}
}
var tokens []AccessToken
lastEight := token[len(token)-8:]
err := x.Table(&AccessToken{}).Where("token_last_eight = ?", lastEight).Find(&tokens)
if err != nil {
return nil, err
} else if !has {
return nil, ErrAccessTokenNotExist{sha}
} else if len(tokens) == 0 {
return nil, ErrAccessTokenNotExist{token}
}
for _, t := range tokens {
tempHash := hashToken(token, t.TokenSalt)
if subtle.ConstantTimeCompare([]byte(t.TokenHash), []byte(tempHash)) == 1 {
return &t, nil
}
}
return t, nil
return nil, ErrAccessTokenNotExist{token}
}

// ListAccessTokens returns a list of access tokens belongs to given user.
Expand Down
9 changes: 5 additions & 4 deletions models/token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ func TestNewAccessToken(t *testing.T) {

func TestGetAccessTokenBySHA(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
token, err := GetAccessTokenBySHA("hash1")
token, err := GetAccessTokenBySHA("d2c6c1ba3890b309189a8e618c72a162e4efbf36")
assert.NoError(t, err)
assert.Equal(t, int64(1), token.UID)
assert.Equal(t, "Token A", token.Name)
assert.Equal(t, "hash1", token.Sha1)
assert.Equal(t, "2b3668e11cb82d3af8c6e4524fc7841297668f5008d1626f0ad3417e9fa39af84c268248b78c481daa7e5dc437784003494f", token.TokenHash)
assert.Equal(t, "e4efbf36", token.TokenLastEight)

token, err = GetAccessTokenBySHA("notahash")
assert.Error(t, err)
Expand Down Expand Up @@ -69,7 +70,7 @@ func TestListAccessTokens(t *testing.T) {

func TestUpdateAccessToken(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
token, err := GetAccessTokenBySHA("hash2")
token, err := GetAccessTokenBySHA("4c6f36e6cf498e2a448662f915d932c09c5a146c")
assert.NoError(t, err)
token.Name = "Token Z"

Expand All @@ -80,7 +81,7 @@ func TestUpdateAccessToken(t *testing.T) {
func TestDeleteAccessTokenByID(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())

token, err := GetAccessTokenBySHA("hash2")
token, err := GetAccessTokenBySHA("4c6f36e6cf498e2a448662f915d932c09c5a146c")
assert.NoError(t, err)
assert.Equal(t, int64(1), token.UID)

Expand Down
8 changes: 8 additions & 0 deletions modules/base/tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"crypto/md5"
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
Expand Down Expand Up @@ -54,6 +55,13 @@ func EncodeSha1(str string) string {
return hex.EncodeToString(h.Sum(nil))
}

// EncodeSha256 string to sha1 hex value.
func EncodeSha256(str string) string {
h := sha256.New()
h.Write([]byte(str))
return hex.EncodeToString(h.Sum(nil))
}

// ShortSha is basically just truncating.
// It is DEPRECATED and will be removed in the future.
func ShortSha(sha1 string) string {
Expand Down
7 changes: 7 additions & 0 deletions modules/base/tool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ func TestEncodeSha1(t *testing.T) {
)
}

func TestEncodeSha256(t *testing.T) {
assert.Equal(t,
"c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2",
EncodeSha256("foobar"),
)
}

func TestShortSha(t *testing.T) {
assert.Equal(t, "veryverylo", ShortSha("veryverylong"))
}
Expand Down
Loading