Browse Source

Git LFS support v2 (#122)

* Import github.com/git-lfs/lfs-test-server as lfs module base

Imported commit is 3968aac269a77b73924649b9412ae03f7ccd3198

Removed:

Dockerfile CONTRIBUTING.md mgmt* script/ vendor/ kvlogger.go
.dockerignore .gitignore README.md

* Remove config, add JWT support from github.com/mgit-at/lfs-test-server

Imported commit f0cdcc5a01599c5a955dc1bbf683bb4acecdba83

* Add LFS settings

* Add LFS meta object model

* Add LFS routes and initialization

* Import github.com/dgrijalva/jwt-go into vendor/

* Adapt LFS module: handlers, routing, meta store

* Move LFS routes to /user/repo/info/lfs/*

* Add request header checks to LFS BatchHandler / PostHandler

* Implement LFS basic authentication

* Rework JWT secret generation / load

* Implement LFS SSH token authentication with JWT

Specification: https://github.com/github/git-lfs/tree/master/docs/api

* Integrate LFS settings into install process

* Remove LFS objects when repository is deleted

Only removes objects from content store when deleted repo is the only
referencing repository

* Make LFS module stateless

Fixes bug where LFS would not work after installation without
restarting Gitea

* Change 500 'Internal Server Error' to 400 'Bad Request'

* Change sql query to xorm call

* Remove unneeded type from LFS module

* Change internal imports to code.gitea.io/gitea/

* Add Gitea authors copyright

* Change basic auth realm to "gitea-lfs"

* Add unique indexes to LFS model

* Use xorm count function in LFS check on repository delete

* Return io.ReadCloser from content store and close after usage

* Add LFS info to runWeb()

* Export LFS content store base path

* LFS file download from UI

* Work around git-lfs client issue with unauthenticated requests

Returning a dummy Authorization header for unauthenticated requests
lets git-lfs client skip asking for auth credentials
See: https://github.com/github/git-lfs/issues/1088

* Fix unauthenticated UI downloads from public repositories

* Authentication check order, Finish LFS file view logic

* Ignore LFS hooks if installed for current OS user

Fixes Gitea UI actions for repositories tracking LFS files.
Checks for minimum needed git version by parsing the semantic version
string.

* Hide LFS metafile diff from commit view, marking as binary

* Show LFS notice if file in commit view is tracked

* Add notbefore/nbf JWT claim

* Correct lint suggestions - comments for structs and functions

- Add comments to LFS model
- Function comment for GetRandomBytesAsBase64
- LFS server function comments and lint variable suggestion

* Move secret generation code out of conditional

Ensures no LFS code may run with an empty secret

* Do not hand out JWT tokens if LFS server support is disabled
Fabian Zaremba 3 years ago
parent
commit
2e7ccecfe6
37 changed files with 2632 additions and 11 deletions
  1. 63 1
      cmd/serve.go
  2. 12 0
      cmd/web.go
  3. 25 0
      models/git_diff.go
  4. 122 0
      models/lfs.go
  5. 1 1
      models/models.go
  6. 57 0
      models/repo.go
  7. 1 0
      modules/auth/user_form.go
  8. 13 0
      modules/base/tool.go
  9. 20 0
      modules/lfs/LICENSE
  10. 94 0
      modules/lfs/content_store.go
  11. 549 0
      modules/lfs/server.go
  12. 90 0
      modules/setting/setting.go
  13. 4 0
      options/locale/locale_en-US.ini
  14. 19 0
      routers/install.go
  15. 27 0
      routers/repo/view.go
  16. 5 0
      templates/install.tmpl
  17. 1 1
      templates/repo/diff/box.tmpl
  18. 2 2
      templates/repo/view_file.tmpl
  19. 8 0
      vendor/github.com/dgrijalva/jwt-go/LICENSE
  20. 97 0
      vendor/github.com/dgrijalva/jwt-go/MIGRATION_GUIDE.md
  21. 85 0
      vendor/github.com/dgrijalva/jwt-go/README.md
  22. 105 0
      vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md
  23. 134 0
      vendor/github.com/dgrijalva/jwt-go/claims.go
  24. 4 0
      vendor/github.com/dgrijalva/jwt-go/doc.go
  25. 147 0
      vendor/github.com/dgrijalva/jwt-go/ecdsa.go
  26. 67 0
      vendor/github.com/dgrijalva/jwt-go/ecdsa_utils.go
  27. 59 0
      vendor/github.com/dgrijalva/jwt-go/errors.go
  28. 94 0
      vendor/github.com/dgrijalva/jwt-go/hmac.go
  29. 94 0
      vendor/github.com/dgrijalva/jwt-go/map_claims.go
  30. 52 0
      vendor/github.com/dgrijalva/jwt-go/none.go
  31. 131 0
      vendor/github.com/dgrijalva/jwt-go/parser.go
  32. 100 0
      vendor/github.com/dgrijalva/jwt-go/rsa.go
  33. 126 0
      vendor/github.com/dgrijalva/jwt-go/rsa_pss.go
  34. 69 0
      vendor/github.com/dgrijalva/jwt-go/rsa_utils.go
  35. 35 0
      vendor/github.com/dgrijalva/jwt-go/signing_method.go
  36. 108 0
      vendor/github.com/dgrijalva/jwt-go/token.go
  37. 12 6
      vendor/vendor.json

+ 63 - 1
cmd/serve.go

@@ -7,6 +7,7 @@ package cmd
7 7
 
8 8
 import (
9 9
 	"crypto/tls"
10
+	"encoding/json"
10 11
 	"fmt"
11 12
 	"os"
12 13
 	"os/exec"
@@ -21,12 +22,14 @@ import (
21 22
 	"code.gitea.io/gitea/modules/log"
22 23
 	"code.gitea.io/gitea/modules/setting"
23 24
 	"github.com/Unknwon/com"
25
+	"github.com/dgrijalva/jwt-go"
24 26
 	gouuid "github.com/satori/go.uuid"
25 27
 	"github.com/urfave/cli"
26 28
 )
27 29
 
28 30
 const (
29
-	accessDenied = "Repository does not exist or you do not have access"
31
+	accessDenied        = "Repository does not exist or you do not have access"
32
+	lfsAuthenticateVerb = "git-lfs-authenticate"
30 33
 )
31 34
 
32 35
 // CmdServ represents the available serv sub-command.
@@ -73,6 +76,7 @@ var (
73 76
 		"git-upload-pack":    models.AccessModeRead,
74 77
 		"git-upload-archive": models.AccessModeRead,
75 78
 		"git-receive-pack":   models.AccessModeWrite,
79
+		lfsAuthenticateVerb:  models.AccessModeNone,
76 80
 	}
77 81
 )
78 82
 
@@ -161,6 +165,21 @@ func runServ(c *cli.Context) error {
161 165
 	}
162 166
 
163 167
 	verb, args := parseCmd(cmd)
168
+
169
+	var lfsVerb string
170
+	if verb == lfsAuthenticateVerb {
171
+
172
+		if !setting.LFS.StartServer {
173
+			fail("Unknown git command", "LFS authentication request over SSH denied, LFS support is disabled")
174
+		}
175
+
176
+		if strings.Contains(args, " ") {
177
+			argsSplit := strings.SplitN(args, " ", 2)
178
+			args = strings.TrimSpace(argsSplit[0])
179
+			lfsVerb = strings.TrimSpace(argsSplit[1])
180
+		}
181
+	}
182
+
164 183
 	repoPath := strings.ToLower(strings.Trim(args, "'"))
165 184
 	rr := strings.SplitN(repoPath, "/", 2)
166 185
 	if len(rr) != 2 {
@@ -196,6 +215,14 @@ func runServ(c *cli.Context) error {
196 215
 		fail("Unknown git command", "Unknown git command %s", verb)
197 216
 	}
198 217
 
218
+	if verb == lfsAuthenticateVerb {
219
+		if lfsVerb == "upload" {
220
+			requestedMode = models.AccessModeWrite
221
+		} else {
222
+			requestedMode = models.AccessModeRead
223
+		}
224
+	}
225
+
199 226
 	// Prohibit push to mirror repositories.
200 227
 	if requestedMode > models.AccessModeRead && repo.IsMirror {
201 228
 		fail("mirror repository is read-only", "")
@@ -261,6 +288,41 @@ func runServ(c *cli.Context) error {
261 288
 		}
262 289
 	}
263 290
 
291
+	//LFS token authentication
292
+
293
+	if verb == lfsAuthenticateVerb {
294
+
295
+		url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, repoUser.Name, repo.Name)
296
+
297
+		now := time.Now()
298
+		token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
299
+			"repo": repo.ID,
300
+			"op":   lfsVerb,
301
+			"exp":  now.Add(5 * time.Minute).Unix(),
302
+			"nbf":  now.Unix(),
303
+		})
304
+
305
+		// Sign and get the complete encoded token as a string using the secret
306
+		tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
307
+		if err != nil {
308
+			fail("Internal error", "Failed to sign JWT token: %v", err)
309
+		}
310
+
311
+		tokenAuthentication := &models.LFSTokenResponse{
312
+			Header: make(map[string]string),
313
+			Href:   url,
314
+		}
315
+		tokenAuthentication.Header["Authorization"] = fmt.Sprintf("Bearer %s", tokenString)
316
+
317
+		enc := json.NewEncoder(os.Stdout)
318
+		err = enc.Encode(tokenAuthentication)
319
+		if err != nil {
320
+			fail("Internal error", "Failed to encode LFS json response: %v", err)
321
+		}
322
+
323
+		return nil
324
+	}
325
+
264 326
 	uuid := gouuid.NewV4().String()
265 327
 	os.Setenv("GITEA_UUID", uuid)
266 328
 	// Keep the old env variable name for backward compability

+ 12 - 0
cmd/web.go

@@ -17,6 +17,7 @@ import (
17 17
 	"code.gitea.io/gitea/models"
18 18
 	"code.gitea.io/gitea/modules/auth"
19 19
 	"code.gitea.io/gitea/modules/context"
20
+	"code.gitea.io/gitea/modules/lfs"
20 21
 	"code.gitea.io/gitea/modules/log"
21 22
 	"code.gitea.io/gitea/modules/options"
22 23
 	"code.gitea.io/gitea/modules/public"
@@ -29,6 +30,7 @@ import (
29 30
 	"code.gitea.io/gitea/routers/org"
30 31
 	"code.gitea.io/gitea/routers/repo"
31 32
 	"code.gitea.io/gitea/routers/user"
33
+
32 34
 	"github.com/go-macaron/binding"
33 35
 	"github.com/go-macaron/cache"
34 36
 	"github.com/go-macaron/captcha"
@@ -564,6 +566,12 @@ func runWeb(ctx *cli.Context) error {
564 566
 		}, ignSignIn, context.RepoAssignment(true), context.RepoRef())
565 567
 
566 568
 		m.Group("/:reponame", func() {
569
+			m.Group("/info/lfs", func() {
570
+				m.Post("/objects/batch", lfs.BatchHandler)
571
+				m.Get("/objects/:oid/:filename", lfs.ObjectOidHandler)
572
+				m.Any("/objects/:oid", lfs.ObjectOidHandler)
573
+				m.Post("/objects", lfs.PostHandler)
574
+			}, ignSignInAndCsrf)
567 575
 			m.Any("/*", ignSignInAndCsrf, repo.HTTP)
568 576
 			m.Head("/tasks/trigger", repo.TriggerTask)
569 577
 		})
@@ -600,6 +608,10 @@ func runWeb(ctx *cli.Context) error {
600 608
 	}
601 609
 	log.Info("Listen: %v://%s%s", setting.Protocol, listenAddr, setting.AppSubURL)
602 610
 
611
+	if setting.LFS.StartServer {
612
+		log.Info("LFS server enabled")
613
+	}
614
+
603 615
 	var err error
604 616
 	switch setting.Protocol {
605 617
 	case setting.HTTP:

+ 25 - 0
models/git_diff.go

@@ -200,6 +200,7 @@ type DiffFile struct {
200 200
 	IsCreated          bool
201 201
 	IsDeleted          bool
202 202
 	IsBin              bool
203
+	IsLFSFile          bool
203 204
 	IsRenamed          bool
204 205
 	IsSubmodule        bool
205 206
 	Sections           []*DiffSection
@@ -245,6 +246,7 @@ func ParsePatch(maxLines, maxLineCharacteres, maxFiles int, reader io.Reader) (*
245 246
 		leftLine, rightLine int
246 247
 		lineCount           int
247 248
 		curFileLinesCount   int
249
+		curFileLFSPrefix    bool
248 250
 	)
249 251
 
250 252
 	input := bufio.NewReader(reader)
@@ -268,6 +270,28 @@ func ParsePatch(maxLines, maxLineCharacteres, maxFiles int, reader io.Reader) (*
268 270
 			continue
269 271
 		}
270 272
 
273
+		trimLine := strings.Trim(line, "+- ")
274
+
275
+		if trimLine == LFSMetaFileIdentifier {
276
+			curFileLFSPrefix = true
277
+		}
278
+
279
+		if curFileLFSPrefix && strings.HasPrefix(trimLine, LFSMetaFileOidPrefix) {
280
+			oid := strings.TrimPrefix(trimLine, LFSMetaFileOidPrefix)
281
+
282
+			if len(oid) == 64 {
283
+				m := &LFSMetaObject{Oid: oid}
284
+				count, err := x.Count(m)
285
+
286
+				if err == nil && count > 0 {
287
+					curFile.IsBin = true
288
+					curFile.IsLFSFile = true
289
+					curSection.Lines = nil
290
+					break
291
+				}
292
+			}
293
+		}
294
+
271 295
 		curFileLinesCount++
272 296
 		lineCount++
273 297
 
@@ -354,6 +378,7 @@ func ParsePatch(maxLines, maxLineCharacteres, maxFiles int, reader io.Reader) (*
354 378
 				break
355 379
 			}
356 380
 			curFileLinesCount = 0
381
+			curFileLFSPrefix = false
357 382
 
358 383
 			// Check file diff type and is submodule.
359 384
 			for {

+ 122 - 0
models/lfs.go

@@ -0,0 +1,122 @@
1
+package models
2
+
3
+import (
4
+	"errors"
5
+	"github.com/go-xorm/xorm"
6
+	"time"
7
+)
8
+
9
+// LFSMetaObject stores metadata for LFS tracked files.
10
+type LFSMetaObject struct {
11
+	ID           int64     `xorm:"pk autoincr"`
12
+	Oid          string    `xorm:"UNIQUE(s) INDEX NOT NULL"`
13
+	Size         int64     `xorm:"NOT NULL"`
14
+	RepositoryID int64     `xorm:"UNIQUE(s) INDEX NOT NULL"`
15
+	Existing     bool      `xorm:"-"`
16
+	Created      time.Time `xorm:"-"`
17
+	CreatedUnix  int64
18
+}
19
+
20
+// LFSTokenResponse defines the JSON structure in which the JWT token is stored.
21
+// This structure is fetched via SSH and passed by the Git LFS client to the server
22
+// endpoint for authorization.
23
+type LFSTokenResponse struct {
24
+	Header map[string]string `json:"header"`
25
+	Href   string            `json:"href"`
26
+}
27
+
28
+var (
29
+	// ErrLFSObjectNotExist is returned from lfs models functions in order
30
+	// to differentiate between database and missing object errors.
31
+	ErrLFSObjectNotExist = errors.New("LFS Meta object does not exist")
32
+)
33
+
34
+const (
35
+	// LFSMetaFileIdentifier is the string appearing at the first line of LFS pointer files.
36
+	// https://github.com/git-lfs/git-lfs/blob/master/docs/spec.md
37
+	LFSMetaFileIdentifier = "version https://git-lfs.github.com/spec/v1"
38
+
39
+	// LFSMetaFileOidPrefix appears in LFS pointer files on a line before the sha256 hash.
40
+	LFSMetaFileOidPrefix = "oid sha256:"
41
+)
42
+
43
+// NewLFSMetaObject stores a given populated LFSMetaObject structure in the database
44
+// if it is not already present.
45
+func NewLFSMetaObject(m *LFSMetaObject) (*LFSMetaObject, error) {
46
+	var err error
47
+
48
+	has, err := x.Get(m)
49
+	if err != nil {
50
+		return nil, err
51
+	}
52
+
53
+	if has {
54
+		m.Existing = true
55
+		return m, nil
56
+	}
57
+
58
+	sess := x.NewSession()
59
+	defer sessionRelease(sess)
60
+	if err = sess.Begin(); err != nil {
61
+		return nil, err
62
+	}
63
+
64
+	if _, err = sess.Insert(m); err != nil {
65
+		return nil, err
66
+	}
67
+
68
+	return m, sess.Commit()
69
+}
70
+
71
+// GetLFSMetaObjectByOid selects a LFSMetaObject entry from database by its OID.
72
+// It may return ErrLFSObjectNotExist or a database error. If the error is nil,
73
+// the returned pointer is a valid LFSMetaObject.
74
+func GetLFSMetaObjectByOid(oid string) (*LFSMetaObject, error) {
75
+	if len(oid) == 0 {
76
+		return nil, ErrLFSObjectNotExist
77
+	}
78
+
79
+	m := &LFSMetaObject{Oid: oid}
80
+	has, err := x.Get(m)
81
+	if err != nil {
82
+		return nil, err
83
+	} else if !has {
84
+		return nil, ErrLFSObjectNotExist
85
+	}
86
+	return m, nil
87
+}
88
+
89
+// RemoveLFSMetaObjectByOid removes a LFSMetaObject entry from database by its OID.
90
+// It may return ErrLFSObjectNotExist or a database error.
91
+func RemoveLFSMetaObjectByOid(oid string) error {
92
+	if len(oid) == 0 {
93
+		return ErrLFSObjectNotExist
94
+	}
95
+
96
+	sess := x.NewSession()
97
+	defer sessionRelease(sess)
98
+	if err := sess.Begin(); err != nil {
99
+		return err
100
+	}
101
+
102
+	m := &LFSMetaObject{Oid: oid}
103
+
104
+	if _, err := sess.Delete(m); err != nil {
105
+		return err
106
+	}
107
+
108
+	return sess.Commit()
109
+}
110
+
111
+// BeforeInsert sets the time at which the LFSMetaObject was created.
112
+func (m *LFSMetaObject) BeforeInsert() {
113
+	m.CreatedUnix = time.Now().Unix()
114
+}
115
+
116
+// AfterSet stores the LFSMetaObject creation time in the database as local time.
117
+func (m *LFSMetaObject) AfterSet(colName string, _ xorm.Cell) {
118
+	switch colName {
119
+	case "created_unix":
120
+		m.Created = time.Unix(m.CreatedUnix, 0).Local()
121
+	}
122
+}

+ 1 - 1
models/models.go

@@ -79,7 +79,7 @@ func init() {
79 79
 		new(Mirror), new(Release), new(LoginSource), new(Webhook),
80 80
 		new(UpdateTask), new(HookTask),
81 81
 		new(Team), new(OrgUser), new(TeamUser), new(TeamRepo),
82
-		new(Notice), new(EmailAddress))
82
+		new(Notice), new(EmailAddress), new(LFSMetaObject))
83 83
 
84 84
 	gonicNames := []string{"SSL", "UID"}
85 85
 	for _, name := range gonicNames {

+ 57 - 0
models/repo.go

@@ -1480,6 +1480,35 @@ func DeleteRepository(uid, repoID int64) error {
1480 1480
 		RemoveAllWithNotice("Delete attachment", attachmentPaths[i])
1481 1481
 	}
1482 1482
 
1483
+	// Remove LFS objects
1484
+	var lfsObjects []*LFSMetaObject
1485
+
1486
+	if err = sess.Where("repository_id=?", repoID).Find(&lfsObjects); err != nil {
1487
+		return err
1488
+	}
1489
+
1490
+	for _, v := range lfsObjects {
1491
+		count, err := sess.Count(&LFSMetaObject{Oid: v.Oid})
1492
+
1493
+		if err != nil {
1494
+			return err
1495
+		}
1496
+
1497
+		if count > 1 {
1498
+			continue
1499
+		}
1500
+
1501
+		oidPath := filepath.Join(v.Oid[0:2], v.Oid[2:4], v.Oid[4:len(v.Oid)])
1502
+		err = os.Remove(filepath.Join(setting.LFS.ContentPath, oidPath))
1503
+		if err != nil {
1504
+			return err
1505
+		}
1506
+	}
1507
+
1508
+	if _, err := sess.Delete(&LFSMetaObject{RepositoryID: repoID}); err != nil {
1509
+		return err
1510
+	}
1511
+
1483 1512
 	if err = sess.Commit(); err != nil {
1484 1513
 		return fmt.Errorf("Commit: %v", err)
1485 1514
 	}
@@ -2240,6 +2269,34 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Reposit
2240 2269
 		return nil, fmt.Errorf("createUpdateHook: %v", err)
2241 2270
 	}
2242 2271
 
2272
+	//Commit repo to get Fork ID
2273
+	err = sess.Commit()
2274
+	if err != nil {
2275
+		return nil, err
2276
+	}
2277
+	sessionRelease(sess)
2278
+
2279
+	// Copy LFS meta objects in new session
2280
+	sess = x.NewSession()
2281
+	defer sessionRelease(sess)
2282
+	if err = sess.Begin(); err != nil {
2283
+		return nil, err
2284
+	}
2285
+
2286
+	var lfsObjects []*LFSMetaObject
2287
+
2288
+	if err = sess.Where("repository_id=?", oldRepo.ID).Find(&lfsObjects); err != nil {
2289
+		return nil, err
2290
+	}
2291
+
2292
+	for _, v := range lfsObjects {
2293
+		v.ID = 0
2294
+		v.RepositoryID = repo.ID
2295
+		if _, err = sess.Insert(v); err != nil {
2296
+			return nil, err
2297
+		}
2298
+	}
2299
+
2243 2300
 	return repo, sess.Commit()
2244 2301
 }
2245 2302
 

+ 1 - 0
modules/auth/user_form.go

@@ -23,6 +23,7 @@ type InstallForm struct {
23 23
 
24 24
 	AppName      string `binding:"Required" locale:"install.app_name"`
25 25
 	RepoRootPath string `binding:"Required"`
26
+	LFSRootPath  string
26 27
 	RunUser      string `binding:"Required"`
27 28
 	Domain       string `binding:"Required"`
28 29
 	SSHPort      int

+ 13 - 0
modules/base/tool.go

@@ -12,6 +12,7 @@ import (
12 12
 	"encoding/hex"
13 13
 	"fmt"
14 14
 	"html/template"
15
+	"io"
15 16
 	"math"
16 17
 	"math/big"
17 18
 	"net/http"
@@ -103,6 +104,18 @@ func GetRandomString(n int) (string, error) {
103 104
 	return string(buffer), nil
104 105
 }
105 106
 
107
+// GetRandomBytesAsBase64 generates a random base64 string from n bytes
108
+func GetRandomBytesAsBase64(n int) string {
109
+	bytes := make([]byte, 32)
110
+	_, err := io.ReadFull(rand.Reader, bytes)
111
+
112
+	if err != nil {
113
+		log.Fatal(4, "Error reading random bytes: %s", err)
114
+	}
115
+
116
+	return base64.RawURLEncoding.EncodeToString(bytes)
117
+}
118
+
106 119
 func randomInt(max *big.Int) (int, error) {
107 120
 	rand, err := rand.Int(rand.Reader, max)
108 121
 	if err != nil {

+ 20 - 0
modules/lfs/LICENSE

@@ -0,0 +1,20 @@
1
+Copyright (c) 2016 The Gitea Authors
2
+Copyright (c) GitHub, Inc. and LFS Test Server contributors
3
+
4
+Permission is hereby granted, free of charge, to any person obtaining a copy
5
+of this software and associated documentation files (the "Software"), to deal
6
+in the Software without restriction, including without limitation the rights
7
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+copies of the Software, and to permit persons to whom the Software is
9
+furnished to do so, subject to the following conditions:
10
+
11
+The above copyright notice and this permission notice shall be included in all
12
+copies or substantial portions of the Software.
13
+
14
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+SOFTWARE.

+ 94 - 0
modules/lfs/content_store.go

@@ -0,0 +1,94 @@
1
+package lfs
2
+
3
+import (
4
+	"code.gitea.io/gitea/models"
5
+	"crypto/sha256"
6
+	"encoding/hex"
7
+	"errors"
8
+	"io"
9
+	"os"
10
+	"path/filepath"
11
+)
12
+
13
+var (
14
+	errHashMismatch = errors.New("Content hash does not match OID")
15
+	errSizeMismatch = errors.New("Content size does not match")
16
+)
17
+
18
+// ContentStore provides a simple file system based storage.
19
+type ContentStore struct {
20
+	BasePath string
21
+}
22
+
23
+// Get takes a Meta object and retreives the content from the store, returning
24
+// it as an io.Reader. If fromByte > 0, the reader starts from that byte
25
+func (s *ContentStore) Get(meta *models.LFSMetaObject, fromByte int64) (io.ReadCloser, error) {
26
+	path := filepath.Join(s.BasePath, transformKey(meta.Oid))
27
+
28
+	f, err := os.Open(path)
29
+	if err != nil {
30
+		return nil, err
31
+	}
32
+	if fromByte > 0 {
33
+		_, err = f.Seek(fromByte, os.SEEK_CUR)
34
+	}
35
+	return f, err
36
+}
37
+
38
+// Put takes a Meta object and an io.Reader and writes the content to the store.
39
+func (s *ContentStore) Put(meta *models.LFSMetaObject, r io.Reader) error {
40
+	path := filepath.Join(s.BasePath, transformKey(meta.Oid))
41
+	tmpPath := path + ".tmp"
42
+
43
+	dir := filepath.Dir(path)
44
+	if err := os.MkdirAll(dir, 0750); err != nil {
45
+		return err
46
+	}
47
+
48
+	file, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0640)
49
+	if err != nil {
50
+		return err
51
+	}
52
+	defer os.Remove(tmpPath)
53
+
54
+	hash := sha256.New()
55
+	hw := io.MultiWriter(hash, file)
56
+
57
+	written, err := io.Copy(hw, r)
58
+	if err != nil {
59
+		file.Close()
60
+		return err
61
+	}
62
+	file.Close()
63
+
64
+	if written != meta.Size {
65
+		return errSizeMismatch
66
+	}
67
+
68
+	shaStr := hex.EncodeToString(hash.Sum(nil))
69
+	if shaStr != meta.Oid {
70
+		return errHashMismatch
71
+	}
72
+
73
+	if err := os.Rename(tmpPath, path); err != nil {
74
+		return err
75
+	}
76
+	return nil
77
+}
78
+
79
+// Exists returns true if the object exists in the content store.
80
+func (s *ContentStore) Exists(meta *models.LFSMetaObject) bool {
81
+	path := filepath.Join(s.BasePath, transformKey(meta.Oid))
82
+	if _, err := os.Stat(path); os.IsNotExist(err) {
83
+		return false
84
+	}
85
+	return true
86
+}
87
+
88
+func transformKey(key string) string {
89
+	if len(key) < 5 {
90
+		return key
91
+	}
92
+
93
+	return filepath.Join(key[0:2], key[2:4], key[4:len(key)])
94
+}

+ 549 - 0
modules/lfs/server.go

@@ -0,0 +1,549 @@
1
+package lfs
2
+
3
+import (
4
+	"encoding/base64"
5
+	"encoding/json"
6
+	"fmt"
7
+	"io"
8
+	"net/http"
9
+	"regexp"
10
+	"strconv"
11
+	"strings"
12
+	"time"
13
+
14
+	"code.gitea.io/gitea/models"
15
+	"code.gitea.io/gitea/modules/context"
16
+	"code.gitea.io/gitea/modules/log"
17
+	"code.gitea.io/gitea/modules/setting"
18
+	"github.com/dgrijalva/jwt-go"
19
+	"gopkg.in/macaron.v1"
20
+)
21
+
22
+const (
23
+	contentMediaType = "application/vnd.git-lfs"
24
+	metaMediaType    = contentMediaType + "+json"
25
+)
26
+
27
+// RequestVars contain variables from the HTTP request. Variables from routing, json body decoding, and
28
+// some headers are stored.
29
+type RequestVars struct {
30
+	Oid           string
31
+	Size          int64
32
+	User          string
33
+	Password      string
34
+	Repo          string
35
+	Authorization string
36
+}
37
+
38
+// BatchVars contains multiple RequestVars processed in one batch operation.
39
+// https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md
40
+type BatchVars struct {
41
+	Transfers []string       `json:"transfers,omitempty"`
42
+	Operation string         `json:"operation"`
43
+	Objects   []*RequestVars `json:"objects"`
44
+}
45
+
46
+// BatchResponse contains multiple object metadata Representation structures
47
+// for use with the batch API.
48
+type BatchResponse struct {
49
+	Transfer string            `json:"transfer,omitempty"`
50
+	Objects  []*Representation `json:"objects"`
51
+}
52
+
53
+// Representation is object medata as seen by clients of the lfs server.
54
+type Representation struct {
55
+	Oid     string           `json:"oid"`
56
+	Size    int64            `json:"size"`
57
+	Actions map[string]*link `json:"actions"`
58
+	Error   *ObjectError     `json:"error,omitempty"`
59
+}
60
+
61
+// ObjectError defines the JSON structure returned to the client in case of an error
62
+type ObjectError struct {
63
+	Code    int    `json:"code"`
64
+	Message string `json:"message"`
65
+}
66
+
67
+// ObjectLink builds a URL linking to the object.
68
+func (v *RequestVars) ObjectLink() string {
69
+	return fmt.Sprintf("%s%s/%s/info/lfs/objects/%s", setting.AppURL, v.User, v.Repo, v.Oid)
70
+}
71
+
72
+// link provides a structure used to build a hypermedia representation of an HTTP link.
73
+type link struct {
74
+	Href      string            `json:"href"`
75
+	Header    map[string]string `json:"header,omitempty"`
76
+	ExpiresAt time.Time         `json:"expires_at,omitempty"`
77
+}
78
+
79
+// ObjectOidHandler is the main request routing entry point into LFS server functions
80
+func ObjectOidHandler(ctx *context.Context) {
81
+
82
+	if !setting.LFS.StartServer {
83
+		writeStatus(ctx, 404)
84
+		return
85
+	}
86
+
87
+	if ctx.Req.Method == "GET" || ctx.Req.Method == "HEAD" {
88
+		if MetaMatcher(ctx.Req) {
89
+			GetMetaHandler(ctx)
90
+			return
91
+		}
92
+		if ContentMatcher(ctx.Req) || len(ctx.Params("filename")) > 0 {
93
+			GetContentHandler(ctx)
94
+			return
95
+		}
96
+	} else if ctx.Req.Method == "PUT" && ContentMatcher(ctx.Req) {
97
+		PutHandler(ctx)
98
+		return
99
+	}
100
+
101
+}
102
+
103
+// GetContentHandler gets the content from the content store
104
+func GetContentHandler(ctx *context.Context) {
105
+
106
+	rv := unpack(ctx)
107
+
108
+	meta, err := models.GetLFSMetaObjectByOid(rv.Oid)
109
+	if err != nil {
110
+		writeStatus(ctx, 404)
111
+		return
112
+	}
113
+
114
+	repository, err := models.GetRepositoryByID(meta.RepositoryID)
115
+
116
+	if err != nil {
117
+		writeStatus(ctx, 404)
118
+		return
119
+	}
120
+
121
+	if !authenticate(ctx, repository, rv.Authorization, false) {
122
+		requireAuth(ctx)
123
+		return
124
+	}
125
+
126
+	// Support resume download using Range header
127
+	var fromByte int64
128
+	statusCode := 200
129
+	if rangeHdr := ctx.Req.Header.Get("Range"); rangeHdr != "" {
130
+		regex := regexp.MustCompile(`bytes=(\d+)\-.*`)
131
+		match := regex.FindStringSubmatch(rangeHdr)
132
+		if match != nil && len(match) > 1 {
133
+			statusCode = 206
134
+			fromByte, _ = strconv.ParseInt(match[1], 10, 32)
135
+			ctx.Resp.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", fromByte, meta.Size-1, int64(meta.Size)-fromByte))
136
+		}
137
+	}
138
+
139
+	contentStore := &ContentStore{BasePath: setting.LFS.ContentPath}
140
+	content, err := contentStore.Get(meta, fromByte)
141
+	if err != nil {
142
+		writeStatus(ctx, 404)
143
+		return
144
+	}
145
+
146
+	ctx.Resp.Header().Set("Content-Length", strconv.FormatInt(meta.Size, 10))
147
+	ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
148
+
149
+	filename := ctx.Params("filename")
150
+	if len(filename) > 0 {
151
+		decodedFilename, err := base64.RawURLEncoding.DecodeString(filename)
152
+		if err == nil {
153
+			ctx.Resp.Header().Set("Content-Disposition", "attachment; filename=\""+string(decodedFilename)+"\"")
154
+		}
155
+	}
156
+
157
+	ctx.Resp.WriteHeader(statusCode)
158
+	io.Copy(ctx.Resp, content)
159
+	content.Close()
160
+	logRequest(ctx.Req, statusCode)
161
+}
162
+
163
+// GetMetaHandler retrieves metadata about the object
164
+func GetMetaHandler(ctx *context.Context) {
165
+
166
+	rv := unpack(ctx)
167
+
168
+	meta, err := models.GetLFSMetaObjectByOid(rv.Oid)
169
+	if err != nil {
170
+		writeStatus(ctx, 404)
171
+		return
172
+	}
173
+
174
+	repository, err := models.GetRepositoryByID(meta.RepositoryID)
175
+
176
+	if err != nil {
177
+		writeStatus(ctx, 404)
178
+		return
179
+	}
180
+
181
+	if !authenticate(ctx, repository, rv.Authorization, false) {
182
+		requireAuth(ctx)
183
+		return
184
+	}
185
+
186
+	ctx.Resp.Header().Set("Content-Type", metaMediaType)
187
+
188
+	if ctx.Req.Method == "GET" {
189
+		enc := json.NewEncoder(ctx.Resp)
190
+		enc.Encode(Represent(rv, meta, true, false))
191
+	}
192
+
193
+	logRequest(ctx.Req, 200)
194
+}
195
+
196
+// PostHandler instructs the client how to upload data
197
+func PostHandler(ctx *context.Context) {
198
+
199
+	if !setting.LFS.StartServer {
200
+		writeStatus(ctx, 404)
201
+		return
202
+	}
203
+
204
+	if !MetaMatcher(ctx.Req) {
205
+		writeStatus(ctx, 400)
206
+		return
207
+	}
208
+
209
+	rv := unpack(ctx)
210
+
211
+	repositoryString := rv.User + "/" + rv.Repo
212
+	repository, err := models.GetRepositoryByRef(repositoryString)
213
+
214
+	if err != nil {
215
+		log.Debug("Could not find repository: %s - %s", repositoryString, err)
216
+		writeStatus(ctx, 404)
217
+		return
218
+	}
219
+
220
+	if !authenticate(ctx, repository, rv.Authorization, true) {
221
+		requireAuth(ctx)
222
+	}
223
+
224
+	meta, err := models.NewLFSMetaObject(&models.LFSMetaObject{Oid: rv.Oid, Size: rv.Size, RepositoryID: repository.ID})
225
+
226
+	if err != nil {
227
+		writeStatus(ctx, 404)
228
+		return
229
+	}
230
+
231
+	ctx.Resp.Header().Set("Content-Type", metaMediaType)
232
+
233
+	sentStatus := 202
234
+	contentStore := &ContentStore{BasePath: setting.LFS.ContentPath}
235
+	if meta.Existing && contentStore.Exists(meta) {
236
+		sentStatus = 200
237
+	}
238
+	ctx.Resp.WriteHeader(sentStatus)
239
+
240
+	enc := json.NewEncoder(ctx.Resp)
241
+	enc.Encode(Represent(rv, meta, meta.Existing, true))
242
+	logRequest(ctx.Req, sentStatus)
243
+}
244
+
245
+// BatchHandler provides the batch api
246
+func BatchHandler(ctx *context.Context) {
247
+
248
+	if !setting.LFS.StartServer {
249
+		writeStatus(ctx, 404)
250
+		return
251
+	}
252
+
253
+	if !MetaMatcher(ctx.Req) {
254
+		writeStatus(ctx, 400)
255
+		return
256
+	}
257
+
258
+	bv := unpackbatch(ctx)
259
+
260
+	var responseObjects []*Representation
261
+
262
+	// Create a response object
263
+	for _, object := range bv.Objects {
264
+
265
+		repositoryString := object.User + "/" + object.Repo
266
+		repository, err := models.GetRepositoryByRef(repositoryString)
267
+
268
+		if err != nil {
269
+			log.Debug("Could not find repository: %s - %s", repositoryString, err)
270
+			writeStatus(ctx, 404)
271
+			return
272
+		}
273
+
274
+		requireWrite := false
275
+		if bv.Operation == "upload" {
276
+			requireWrite = true
277
+		}
278
+
279
+		if !authenticate(ctx, repository, object.Authorization, requireWrite) {
280
+			requireAuth(ctx)
281
+			return
282
+		}
283
+
284
+		meta, err := models.GetLFSMetaObjectByOid(object.Oid)
285
+
286
+		contentStore := &ContentStore{BasePath: setting.LFS.ContentPath}
287
+		if err == nil && contentStore.Exists(meta) { // Object is found and exists
288
+			responseObjects = append(responseObjects, Represent(object, meta, true, false))
289
+			continue
290
+		}
291
+
292
+		// Object is not found
293
+		meta, err = models.NewLFSMetaObject(&models.LFSMetaObject{Oid: object.Oid, Size: object.Size, RepositoryID: repository.ID})
294
+
295
+		if err == nil {
296
+			responseObjects = append(responseObjects, Represent(object, meta, meta.Existing, true))
297
+		}
298
+	}
299
+
300
+	ctx.Resp.Header().Set("Content-Type", metaMediaType)
301
+
302
+	respobj := &BatchResponse{Objects: responseObjects}
303
+
304
+	enc := json.NewEncoder(ctx.Resp)
305
+	enc.Encode(respobj)
306
+	logRequest(ctx.Req, 200)
307
+}
308
+
309
+// PutHandler receives data from the client and puts it into the content store
310
+func PutHandler(ctx *context.Context) {
311
+	rv := unpack(ctx)
312
+
313
+	meta, err := models.GetLFSMetaObjectByOid(rv.Oid)
314
+
315
+	if err != nil {
316
+		writeStatus(ctx, 404)
317
+		return
318
+	}
319
+
320
+	repository, err := models.GetRepositoryByID(meta.RepositoryID)
321
+
322
+	if err != nil {
323
+		writeStatus(ctx, 404)
324
+		return
325
+	}
326
+
327
+	if !authenticate(ctx, repository, rv.Authorization, true) {
328
+		requireAuth(ctx)
329
+		return
330
+	}
331
+
332
+	contentStore := &ContentStore{BasePath: setting.LFS.ContentPath}
333
+	if err := contentStore.Put(meta, ctx.Req.Body().ReadCloser()); err != nil {
334
+		models.RemoveLFSMetaObjectByOid(rv.Oid)
335
+		ctx.Resp.WriteHeader(500)
336
+		fmt.Fprintf(ctx.Resp, `{"message":"%s"}`, err)
337
+		return
338
+	}
339
+
340
+	logRequest(ctx.Req, 200)
341
+}
342
+
343
+// Represent takes a RequestVars and Meta and turns it into a Representation suitable
344
+// for json encoding
345
+func Represent(rv *RequestVars, meta *models.LFSMetaObject, download, upload bool) *Representation {
346
+	rep := &Representation{
347
+		Oid:     meta.Oid,
348
+		Size:    meta.Size,
349
+		Actions: make(map[string]*link),
350
+	}
351
+
352
+	header := make(map[string]string)
353
+	header["Accept"] = contentMediaType
354
+
355
+	if rv.Authorization == "" {
356
+		//https://github.com/github/git-lfs/issues/1088
357
+		header["Authorization"] = "Authorization: Basic dummy"
358
+	} else {
359
+		header["Authorization"] = rv.Authorization
360
+	}
361
+
362
+	if download {
363
+		rep.Actions["download"] = &link{Href: rv.ObjectLink(), Header: header}
364
+	}
365
+
366
+	if upload {
367
+		rep.Actions["upload"] = &link{Href: rv.ObjectLink(), Header: header}
368
+	}
369
+
370
+	return rep
371
+}
372
+
373
+// ContentMatcher provides a mux.MatcherFunc that only allows requests that contain
374
+// an Accept header with the contentMediaType
375
+func ContentMatcher(r macaron.Request) bool {
376
+	mediaParts := strings.Split(r.Header.Get("Accept"), ";")
377
+	mt := mediaParts[0]
378
+	return mt == contentMediaType
379
+}
380
+
381
+// MetaMatcher provides a mux.MatcherFunc that only allows requests that contain
382
+// an Accept header with the metaMediaType
383
+func MetaMatcher(r macaron.Request) bool {
384
+	mediaParts := strings.Split(r.Header.Get("Accept"), ";")
385
+	mt := mediaParts[0]
386
+	return mt == metaMediaType
387
+}
388
+
389
+func unpack(ctx *context.Context) *RequestVars {
390
+	r := ctx.Req
391
+	rv := &RequestVars{
392
+		User:          ctx.Params("username"),
393
+		Repo:          strings.TrimSuffix(ctx.Params("reponame"), ".git"),
394
+		Oid:           ctx.Params("oid"),
395
+		Authorization: r.Header.Get("Authorization"),
396
+	}
397
+
398
+	if r.Method == "POST" { // Maybe also check if +json
399
+		var p RequestVars
400
+		dec := json.NewDecoder(r.Body().ReadCloser())
401
+		err := dec.Decode(&p)
402
+		if err != nil {
403
+			return rv
404
+		}
405
+
406
+		rv.Oid = p.Oid
407
+		rv.Size = p.Size
408
+	}
409
+
410
+	return rv
411
+}
412
+
413
+// TODO cheap hack, unify with unpack
414
+func unpackbatch(ctx *context.Context) *BatchVars {
415
+
416
+	r := ctx.Req
417
+	var bv BatchVars
418
+
419
+	dec := json.NewDecoder(r.Body().ReadCloser())
420
+	err := dec.Decode(&bv)
421
+	if err != nil {
422
+		return &bv
423
+	}
424
+
425
+	for i := 0; i < len(bv.Objects); i++ {
426
+		bv.Objects[i].User = ctx.Params("username")
427
+		bv.Objects[i].Repo = strings.TrimSuffix(ctx.Params("reponame"), ".git")
428
+		bv.Objects[i].Authorization = r.Header.Get("Authorization")
429
+	}
430
+
431
+	return &bv
432
+}
433
+
434
+func writeStatus(ctx *context.Context, status int) {
435
+	message := http.StatusText(status)
436
+
437
+	mediaParts := strings.Split(ctx.Req.Header.Get("Accept"), ";")
438
+	mt := mediaParts[0]
439
+	if strings.HasSuffix(mt, "+json") {
440
+		message = `{"message":"` + message + `"}`
441
+	}
442
+
443
+	ctx.Resp.WriteHeader(status)
444
+	fmt.Fprint(ctx.Resp, message)
445
+	logRequest(ctx.Req, status)
446
+}
447
+
448
+func logRequest(r macaron.Request, status int) {
449
+	log.Debug("LFS request - Method: %s, URL: %s, Status %d", r.Method, r.URL, status)
450
+}
451
+
452
+// authenticate uses the authorization string to determine whether
453
+// or not to proceed. This server assumes an HTTP Basic auth format.
454
+func authenticate(ctx *context.Context, repository *models.Repository, authorization string, requireWrite bool) bool {
455
+
456
+	accessMode := models.AccessModeRead
457
+	if requireWrite {
458
+		accessMode = models.AccessModeWrite
459
+	}
460
+
461
+	if !repository.IsPrivate && !requireWrite {
462
+		return true
463
+	}
464
+
465
+	if ctx.IsSigned {
466
+		accessCheck, _ := models.HasAccess(ctx.User, repository, accessMode)
467
+		return accessCheck
468
+	}
469
+
470
+	if authorization == "" {
471
+		return false
472
+	}
473
+
474
+	if authenticateToken(repository, authorization, requireWrite) {
475
+		return true
476
+	}
477
+
478
+	if !strings.HasPrefix(authorization, "Basic ") {
479
+		return false
480
+	}
481
+
482
+	c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(authorization, "Basic "))
483
+	if err != nil {
484
+		return false
485
+	}
486
+	cs := string(c)
487
+	i := strings.IndexByte(cs, ':')
488
+	if i < 0 {
489
+		return false
490
+	}
491
+	user, password := cs[:i], cs[i+1:]
492
+
493
+	userModel, err := models.GetUserByName(user)
494
+	if err != nil {
495
+		return false
496
+	}
497
+
498
+	if !userModel.ValidatePassword(password) {
499
+		return false
500
+	}
501
+
502
+	accessCheck, _ := models.HasAccess(userModel, repository, accessMode)
503
+	return accessCheck
504
+}
505
+
506
+func authenticateToken(repository *models.Repository, authorization string, requireWrite bool) bool {
507
+	if !strings.HasPrefix(authorization, "Bearer ") {
508
+		return false
509
+	}
510
+
511
+	token, err := jwt.Parse(authorization[7:], func(t *jwt.Token) (interface{}, error) {
512
+		if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
513
+			return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
514
+		}
515
+		return setting.LFS.JWTSecretBytes, nil
516
+	})
517
+	if err != nil {
518
+		return false
519
+	}
520
+	claims, claimsOk := token.Claims.(jwt.MapClaims)
521
+	if !token.Valid || !claimsOk {
522
+		return false
523
+	}
524
+
525
+	opStr, ok := claims["op"].(string)
526
+	if !ok {
527
+		return false
528
+	}
529
+
530
+	if requireWrite && opStr != "upload" {
531
+		return false
532
+	}
533
+
534
+	repoID, ok := claims["repo"].(float64)
535
+	if !ok {
536
+		return false
537
+	}
538
+
539
+	if repository.ID != int64(repoID) {
540
+		return false
541
+	}
542
+
543
+	return true
544
+}
545
+
546
+func requireAuth(ctx *context.Context) {
547
+	ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
548
+	writeStatus(ctx, 401)
549
+}

+ 90 - 0
modules/setting/setting.go

@@ -5,7 +5,10 @@
5 5
 package setting
6 6
 
7 7
 import (
8
+	"crypto/rand"
9
+	"encoding/base64"
8 10
 	"fmt"
11
+	"io"
9 12
 	"net/mail"
10 13
 	"net/url"
11 14
 	"os"
@@ -17,6 +20,7 @@ import (
17 20
 	"strings"
18 21
 	"time"
19 22
 
23
+	"code.gitea.io/git"
20 24
 	"code.gitea.io/gitea/modules/log"
21 25
 	"code.gitea.io/gitea/modules/user"
22 26
 	"github.com/Unknwon/com"
@@ -89,6 +93,13 @@ var (
89 93
 		MinimumKeySizes     map[string]int `ini:"-"`
90 94
 	}
91 95
 
96
+	LFS struct {
97
+		StartServer     bool   `ini:"LFS_START_SERVER"`
98
+		ContentPath     string `ini:"LFS_CONTENT_PATH"`
99
+		JWTSecretBase64 string `ini:"LFS_JWT_SECRET"`
100
+		JWTSecretBytes  []byte `ini:"-"`
101
+	}
102
+
92 103
 	// Security settings
93 104
 	InstallLock          bool
94 105
 	SecretKey            string
@@ -583,6 +594,85 @@ please consider changing to GITEA_CUSTOM`)
583 594
 		}
584 595
 	}
585 596
 
597
+	if err = Cfg.Section("server").MapTo(&LFS); err != nil {
598
+		log.Fatal(4, "Fail to map LFS settings: %v", err)
599
+	}
600
+
601
+	if LFS.StartServer {
602
+
603
+		if err := os.MkdirAll(LFS.ContentPath, 0700); err != nil {
604
+			log.Fatal(4, "Fail to create '%s': %v", LFS.ContentPath, err)
605
+		}
606
+
607
+		LFS.JWTSecretBytes = make([]byte, 32)
608
+		n, err := base64.RawURLEncoding.Decode(LFS.JWTSecretBytes, []byte(LFS.JWTSecretBase64))
609
+
610
+		if err != nil || n != 32 {
611
+			//Generate new secret and save to config
612
+
613
+			_, err := io.ReadFull(rand.Reader, LFS.JWTSecretBytes)
614
+
615
+			if err != nil {
616
+				log.Fatal(4, "Error reading random bytes: %s", err)
617
+			}
618
+
619
+			LFS.JWTSecretBase64 = base64.RawURLEncoding.EncodeToString(LFS.JWTSecretBytes)
620
+
621
+			// Save secret
622
+			cfg := ini.Empty()
623
+			if com.IsFile(CustomConf) {
624
+				// Keeps custom settings if there is already something.
625
+				if err := cfg.Append(CustomConf); err != nil {
626
+					log.Error(4, "Fail to load custom conf '%s': %v", CustomConf, err)
627
+				}
628
+			}
629
+
630
+			cfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
631
+
632
+			os.MkdirAll(filepath.Dir(CustomConf), os.ModePerm)
633
+			if err := cfg.SaveTo(CustomConf); err != nil {
634
+				log.Fatal(4, "Error saving generated JWT Secret to custom config: %v", err)
635
+				return
636
+			}
637
+		}
638
+
639
+		//Disable LFS client hooks if installed for the current OS user
640
+		//Needs at least git v2.1.2
641
+
642
+		binVersion, err := git.BinVersion()
643
+		if err != nil {
644
+			log.Fatal(4, "Error retrieving git version: %s", err)
645
+		}
646
+
647
+		splitVersion := strings.SplitN(binVersion, ".", 3)
648
+
649
+		majorVersion, err := strconv.ParseUint(splitVersion[0], 10, 64)
650
+		if err != nil {
651
+			log.Fatal(4, "Error parsing git major version: %s", err)
652
+		}
653
+		minorVersion, err := strconv.ParseUint(splitVersion[1], 10, 64)
654
+		if err != nil {
655
+			log.Fatal(4, "Error parsing git minor version: %s", err)
656
+		}
657
+		revisionVersion, err := strconv.ParseUint(splitVersion[2], 10, 64)
658
+		if err != nil {
659
+			log.Fatal(4, "Error parsing git revision version: %s", err)
660
+		}
661
+
662
+		if !((majorVersion > 2) || (majorVersion == 2 && minorVersion > 1) ||
663
+			(majorVersion == 2 && minorVersion == 1 && revisionVersion >= 2)) {
664
+
665
+			LFS.StartServer = false
666
+			log.Error(4, "LFS server support needs at least Git v2.1.2")
667
+
668
+		} else {
669
+
670
+			git.GlobalCommandArgs = append(git.GlobalCommandArgs, "-c", "filter.lfs.required=",
671
+				"-c", "filter.lfs.smudge=", "-c", "filter.lfs.clean=")
672
+
673
+		}
674
+	}
675
+
586 676
 	sec = Cfg.Section("security")
587 677
 	InstallLock = sec.Key("INSTALL_LOCK").MustBool(false)
588 678
 	SecretKey = sec.Key("SECRET_KEY").MustString("!#@FDEWREWR&*(")

+ 4 - 0
options/locale/locale_en-US.ini

@@ -69,6 +69,8 @@ app_name = Application Name
69 69
 app_name_helper = Put your organization name here huge and loud!
70 70
 repo_path = Repository Root Path
71 71
 repo_path_helper = All Git remote repositories will be saved to this directory.
72
+lfs_path = LFS Root Path
73
+lfs_path_helper = Files stored with Git LFS will be stored in this directory. Leave empty to disable LFS.
72 74
 run_user = Run User
73 75
 run_user_helper = The user must have access to Repository Root Path and run Gitea.
74 76
 domain = Domain
@@ -432,6 +434,7 @@ file_view_raw = View Raw
432 434
 file_permalink = Permalink
433 435
 file_too_large = This file is too large to be shown
434 436
 video_not_supported_in_browser = Your browser doesn't support HTML5 video tag.
437
+stored_lfs = Stored with Git LFS
435 438
 
436 439
 editor.new_file = New file
437 440
 editor.upload_file = Upload file
@@ -1060,6 +1063,7 @@ config.disable_router_log = Disable Router Log
1060 1063
 config.run_user = Run User
1061 1064
 config.run_mode = Run Mode
1062 1065
 config.repo_root_path = Repository Root Path
1066
+config.lfs_root_path = LFS Root Path
1063 1067
 config.static_file_root_path = Static File Root Path
1064 1068
 config.log_file_root_path = Log File Root Path
1065 1069
 config.script_type = Script Type

+ 19 - 0
routers/install.go

@@ -79,6 +79,7 @@ func Install(ctx *context.Context) {
79 79
 	// Application general settings
80 80
 	form.AppName = setting.AppName
81 81
 	form.RepoRootPath = setting.RepoRootPath
82
+	form.LFSRootPath = setting.LFS.ContentPath
82 83
 
83 84
 	// Note(unknwon): it's hard for Windows users change a running user,
84 85
 	// 	so just use current one if config says default.
@@ -183,6 +184,16 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) {
183 184
 		return
184 185
 	}
185 186
 
187
+	// Test LFS root path if not empty, empty meaning disable LFS
188
+	if form.LFSRootPath != "" {
189
+		form.LFSRootPath = strings.Replace(form.LFSRootPath, "\\", "/", -1)
190
+		if err := os.MkdirAll(form.LFSRootPath, os.ModePerm); err != nil {
191
+			ctx.Data["Err_LFSRootPath"] = true
192
+			ctx.RenderWithErr(ctx.Tr("install.invalid_lfs_path", err), tplInstall, &form)
193
+			return
194
+		}
195
+	}
196
+
186 197
 	// Test log root path.
187 198
 	form.LogRootPath = strings.Replace(form.LogRootPath, "\\", "/", -1)
188 199
 	if err = os.MkdirAll(form.LogRootPath, os.ModePerm); err != nil {
@@ -254,6 +265,14 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) {
254 265
 		cfg.Section("server").Key("SSH_PORT").SetValue(com.ToStr(form.SSHPort))
255 266
 	}
256 267
 
268
+	if form.LFSRootPath != "" {
269
+		cfg.Section("server").Key("LFS_START_SERVER").SetValue("true")
270
+		cfg.Section("server").Key("LFS_CONTENT_PATH").SetValue(form.LFSRootPath)
271
+		cfg.Section("server").Key("LFS_JWT_SECRET").SetValue(base.GetRandomBytesAsBase64(32))
272
+	} else {
273
+		cfg.Section("server").Key("LFS_START_SERVER").SetValue("false")
274
+	}
275
+
257 276
 	if len(strings.TrimSpace(form.SMTPHost)) > 0 {
258 277
 		cfg.Section("mailer").Key("ENABLED").SetValue("true")
259 278
 		cfg.Section("mailer").Key("HOST").SetValue(form.SMTPHost)

+ 27 - 0
routers/repo/view.go

@@ -17,11 +17,14 @@ import (
17 17
 	"code.gitea.io/gitea/modules/base"
18 18
 	"code.gitea.io/gitea/modules/context"
19 19
 	"code.gitea.io/gitea/modules/highlight"
20
+	"code.gitea.io/gitea/modules/lfs"
20 21
 	"code.gitea.io/gitea/modules/log"
21 22
 	"code.gitea.io/gitea/modules/markdown"
22 23
 	"code.gitea.io/gitea/modules/setting"
23 24
 	"code.gitea.io/gitea/modules/templates"
25
+	"encoding/base64"
24 26
 	"github.com/Unknwon/paginater"
27
+	"strconv"
25 28
 )
26 29
 
27 30
 const (
@@ -139,6 +142,30 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
139 142
 	isTextFile := base.IsTextFile(buf)
140 143
 	ctx.Data["IsTextFile"] = isTextFile
141 144
 
145
+	//Check for LFS meta file
146
+	if isTextFile && setting.LFS.StartServer {
147
+		headString := string(buf)
148
+		if strings.HasPrefix(headString, models.LFSMetaFileIdentifier) {
149
+			splitLines := strings.Split(headString, "\n")
150
+			if len(splitLines) >= 3 {
151
+				oid := strings.TrimPrefix(splitLines[1], models.LFSMetaFileOidPrefix)
152
+				size, err := strconv.ParseInt(strings.TrimPrefix(splitLines[2], "size "), 10, 64)
153
+				if len(oid) == 64 && err == nil {
154
+					contentStore := &lfs.ContentStore{BasePath: setting.LFS.ContentPath}
155
+					meta := &models.LFSMetaObject{Oid: oid}
156
+					if contentStore.Exists(meta) {
157
+						ctx.Data["IsTextFile"] = false
158
+						isTextFile = false
159
+						ctx.Data["IsLFSFile"] = true
160
+						ctx.Data["FileSize"] = size
161
+						filenameBase64 := base64.RawURLEncoding.EncodeToString([]byte(blob.Name()))
162
+						ctx.Data["RawFileLink"] = fmt.Sprintf("%s%s/info/lfs/objects/%s/%s", setting.AppURL, ctx.Repo.Repository.FullName(), oid, filenameBase64)
163
+					}
164
+				}
165
+			}
166
+		}
167
+	}
168
+
142 169
 	// Assume file is not editable first.
143 170
 	if !isTextFile {
144 171
 		ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_non_text_files")

+ 5 - 0
templates/install.tmpl

@@ -84,6 +84,11 @@
84 84
 						<input id="repo_root_path" name="repo_root_path" value="{{.repo_root_path}}" required>
85 85
 						<span class="help">{{.i18n.Tr "install.repo_path_helper"}}</span>
86 86
 					</div>
87
+					<div class="inline field {{if .Err_LFSRootPath}}error{{end}}">
88
+          	<label for="lfs_root_path">{{.i18n.Tr "install.lfs_path"}}</label>
89
+          	<input id="lfs_root_path" name="lfs_root_path" value="{{.lfs_root_path}}">
90
+          	<span class="help">{{.i18n.Tr "install.lfs_path_helper"}}</span>
91
+          </div>
87 92
 					<div class="inline required field {{if .Err_RunUser}}error{{end}}">
88 93
 						<label for="run_user">{{.i18n.Tr "install.run_user"}}</label>
89 94
 						<input id="run_user" name="run_user" value="{{.run_user}}" required>

+ 1 - 1
templates/repo/diff/box.tmpl

@@ -66,7 +66,7 @@
66 66
 							<span class="del" data-line="{{.Deletion}}">- {{.Deletion}}</span>
67 67
 						{{end}}
68 68
 					</div>
69
-					<span class="file">{{if $file.IsRenamed}}{{$file.OldName}} &rarr; {{end}}{{$file.Name}}</span>
69
+					<span class="file">{{if $file.IsRenamed}}{{$file.OldName}} &rarr; {{end}}{{$file.Name}}{{if .IsLFSFile}} ({{$.i18n.Tr "repo.stored_lfs"}}){{end}}</span>
70 70
 					{{if not $file.IsSubmodule}}
71 71
 						<div class="ui right">
72 72
 							{{if $file.IsDeleted}}

+ 2 - 2
templates/repo/view_file.tmpl

@@ -5,11 +5,11 @@
5 5
 			{{if .ReadmeInList}}
6 6
 				<strong>{{.FileName}}</strong>
7 7
 			{{else}}
8
-				<strong>{{.FileName}}</strong> <span class="text grey normal">{{FileSize .FileSize}}</span>
8
+				<strong>{{.FileName}}</strong> <span class="text grey normal">{{FileSize .FileSize}}{{if .IsLFSFile}} ({{.i18n.Tr "repo.stored_lfs"}}){{end}}</span>
9 9
 			{{end}}
10 10
 		{{else}}
11 11
 			<i class="file text outline icon ui left"></i>
12
-			<strong>{{.FileName}}</strong> <span class="text grey normal">{{FileSize .FileSize}}</span>
12
+			<strong>{{.FileName}}</strong> <span class="text grey normal">{{FileSize .FileSize}}{{if .IsLFSFile}} ({{.i18n.Tr "repo.stored_lfs"}}){{end}}</span>
13 13
 		{{end}}
14 14
 		{{if not .ReadmeInList}}
15 15
 			<div class="ui right file-actions">

+ 8 - 0
vendor/github.com/dgrijalva/jwt-go/LICENSE

@@ -0,0 +1,8 @@
1
+Copyright (c) 2012 Dave Grijalva
2
+
3
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8
+

+ 97 - 0
vendor/github.com/dgrijalva/jwt-go/MIGRATION_GUIDE.md

@@ -0,0 +1,97 @@
1
+## Migration Guide from v2 -> v3
2
+
3
+Version 3 adds several new, frequently requested features.  To do so, it introduces a few breaking changes.  We've worked to keep these as minimal as possible.  This guide explains the breaking changes and how you can quickly update your code.
4
+
5
+### `Token.Claims` is now an interface type
6
+
7
+The most requested feature from the 2.0 verison of this library was the ability to provide a custom type to the JSON parser for claims. This was implemented by introducing a new interface, `Claims`, to replace `map[string]interface{}`.  We also included two concrete implementations of `Claims`: `MapClaims` and `StandardClaims`.
8
+
9
+`MapClaims` is an alias for `map[string]interface{}` with built in validation behavior.  It is the default claims type when using `Parse`.  The usage is unchanged except you must type cast the claims property.
10
+
11
+The old example for parsing a token looked like this..
12
+
13
+```go
14
+	if token, err := jwt.Parse(tokenString, keyLookupFunc); err == nil {
15
+		fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"])
16
+	}
17
+```
18
+
19
+is now directly mapped to...
20
+
21
+```go
22
+	if token, err := jwt.Parse(tokenString, keyLookupFunc); err == nil {
23
+		claims := token.Claims.(jwt.MapClaims)
24
+		fmt.Printf("Token for user %v expires %v", claims["user"], claims["exp"])
25
+	}
26
+```
27
+
28
+`StandardClaims` is designed to be embedded in your custom type.  You can supply a custom claims type with the new `ParseWithClaims` function.  Here's an example of using a custom claims type.
29
+
30
+```go
31
+	type MyCustomClaims struct {
32
+		User string
33
+		*StandardClaims
34
+	}
35
+	
36
+	if token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, keyLookupFunc); err == nil {
37
+		claims := token.Claims.(*MyCustomClaims)
38
+		fmt.Printf("Token for user %v expires %v", claims.User, claims.StandardClaims.ExpiresAt)
39
+	}
40
+```
41
+
42
+### `ParseFromRequest` has been moved
43
+
44
+To keep this library focused on the tokens without becoming overburdened with complex request processing logic, `ParseFromRequest` and its new companion `ParseFromRequestWithClaims` have been moved to a subpackage, `request`.  The method signatues have also been augmented to receive a new argument: `Extractor`.
45
+
46
+`Extractors` do the work of picking the token string out of a request.  The interface is simple and composable.
47
+
48
+This simple parsing example:
49
+
50
+```go
51
+	if token, err := jwt.ParseFromRequest(tokenString, req, keyLookupFunc); err == nil {
52
+		fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"])
53
+	}
54
+```
55
+
56
+is directly mapped to:
57
+
58
+```go
59
+	if token, err := request.ParseFromRequest(req, request.OAuth2Extractor, keyLookupFunc); err == nil {
60
+		claims := token.Claims.(jwt.MapClaims)
61
+		fmt.Printf("Token for user %v expires %v", claims["user"], claims["exp"])
62
+	}
63
+```
64
+
65
+There are several concrete `Extractor` types provided for your convenience:
66
+
67
+* `HeaderExtractor` will search a list of headers until one contains content.
68
+* `ArgumentExtractor` will search a list of keys in request query and form arguments until one contains content.
69
+* `MultiExtractor` will try a list of `Extractors` in order until one returns content.
70
+* `AuthorizationHeaderExtractor` will look in the `Authorization` header for a `Bearer` token.
71
+* `OAuth2Extractor` searches the places an OAuth2 token would be specified (per the spec): `Authorization` header and `access_token` argument
72
+* `PostExtractionFilter` wraps an `Extractor`, allowing you to process the content before it's parsed.  A simple example is stripping the `Bearer ` text from a header
73
+
74
+
75
+### RSA signing methods no longer accept `[]byte` keys
76
+
77
+Due to a [critical vulnerability](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/), we've decided the convenience of accepting `[]byte` instead of `rsa.PublicKey` or `rsa.PrivateKey` isn't worth the risk of misuse.
78
+
79
+To replace this behavior, we've added two helper methods: `ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error)` and `ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error)`.  These are just simple helpers for unpacking PEM encoded PKCS1 and PKCS8 keys. If your keys are encoded any other way, all you need to do is convert them to the `crypto/rsa` package's types.
80
+
81
+```go 
82
+	func keyLookupFunc(*Token) (interface{}, error) {
83
+		// Don't forget to validate the alg is what you expect:
84
+		if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
85
+			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
86
+		}
87
+		
88
+		// Look up key 
89
+		key, err := lookupPublicKey(token.Header["kid"])
90
+		if err != nil {
91
+			return nil, err
92
+		}
93
+		
94
+		// Unpack key from PEM encoded PKCS8
95
+		return jwt.ParseRSAPublicKeyFromPEM(key)
96
+	}
97
+```

File diff suppressed because it is too large
+ 85 - 0
vendor/github.com/dgrijalva/jwt-go/README.md


File diff suppressed because it is too large
+ 105 - 0
vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md


+ 134 - 0
vendor/github.com/dgrijalva/jwt-go/claims.go

@@ -0,0 +1,134 @@
1
+package jwt
2
+
3
+import (
4
+	"crypto/subtle"
5
+	"fmt"
6
+	"time"
7
+)
8
+
9
+// For a type to be a Claims object, it must just have a Valid method that determines
10
+// if the token is invalid for any supported reason
11
+type Claims interface {
12
+	Valid() error
13
+}
14
+
15
+// Structured version of Claims Section, as referenced at
16
+// https://tools.ietf.org/html/rfc7519#section-4.1
17
+// See examples for how to use this with your own claim types
18
+type StandardClaims struct {
19
+	Audience  string `json:"aud,omitempty"`
20
+	ExpiresAt int64  `json:"exp,omitempty"`
21
+	Id        string `json:"jti,omitempty"`
22
+	IssuedAt  int64  `json:"iat,omitempty"`
23
+	Issuer    string `json:"iss,omitempty"`
24
+	NotBefore int64  `json:"nbf,omitempty"`
25
+	Subject   string `json:"sub,omitempty"`
26
+}
27
+
28
+// Validates time based claims "exp, iat, nbf".
29
+// There is no accounting for clock skew.
30
+// As well, if any of the above claims are not in the token, it will still
31
+// be considered a valid claim.
32
+func (c StandardClaims) Valid() error {
33
+	vErr := new(ValidationError)
34
+	now := TimeFunc().Unix()
35
+
36
+	// The claims below are optional, by default, so if they are set to the
37
+	// default value in Go, let's not fail the verification for them.
38
+	if c.VerifyExpiresAt(now, false) == false {
39
+		delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0))
40
+		vErr.Inner = fmt.Errorf("token is expired by %v", delta)
41
+		vErr.Errors |= ValidationErrorExpired
42
+	}
43
+
44
+	if c.VerifyIssuedAt(now, false) == false {
45
+		vErr.Inner = fmt.Errorf("Token used before issued")
46
+		vErr.Errors |= ValidationErrorIssuedAt
47
+	}
48
+
49
+	if c.VerifyNotBefore(now, false) == false {
50
+		vErr.Inner = fmt.Errorf("token is not valid yet")
51
+		vErr.Errors |= ValidationErrorNotValidYet
52
+	}
53
+
54
+	if vErr.valid() {
55
+		return nil
56
+	}
57
+
58
+	return vErr
59
+}
60
+
61
+// Compares the aud claim against cmp.
62
+// If required is false, this method will return true if the value matches or is unset
63
+func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool {
64
+	return verifyAud(c.Audience, cmp, req)
65
+}
66
+
67
+// Compares the exp claim against cmp.
68
+// If required is false, this method will return true if the value matches or is unset
69
+func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool) bool {
70
+	return verifyExp(c.ExpiresAt, cmp, req)
71
+}
72
+
73
+// Compares the iat claim against cmp.
74
+// If required is false, this method will return true if the value matches or is unset
75
+func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool {
76
+	return verifyIat(c.IssuedAt, cmp, req)
77
+}
78
+
79
+// Compares the iss claim against cmp.
80
+// If required is false, this method will return true if the value matches or is unset
81
+func (c *StandardClaims) VerifyIssuer(cmp string, req bool) bool {
82
+	return verifyIss(c.Issuer, cmp, req)
83
+}
84
+
85
+// Compares the nbf claim against cmp.
86
+// If required is false, this method will return true if the value matches or is unset
87
+func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool {
88
+	return verifyNbf(c.NotBefore, cmp, req)
89
+}
90
+
91
+// ----- helpers
92
+
93
+func verifyAud(aud string, cmp string, required bool) bool {
94
+	if aud == "" {
95
+		return !required
96
+	}
97
+	if subtle.ConstantTimeCompare([]byte(aud), []byte(cmp)) != 0 {
98
+		return true
99
+	} else {
100
+		return false
101
+	}
102
+}
103
+
104
+func verifyExp(exp int64, now int64, required bool) bool {
105
+	if exp == 0 {
106
+		return !required
107
+	}
108
+	return now <= exp
109
+}
110
+
111
+func verifyIat(iat int64, now int64, required bool) bool {
112
+	if iat == 0 {
113
+		return !required
114
+	}
115
+	return now >= iat
116
+}
117
+
118
+func verifyIss(iss string, cmp string, required bool) bool {
119
+	if iss == "" {
120
+		return !required
121
+	}
122
+	if subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0 {
123
+		return true
124
+	} else {
125
+		return false
126
+	}
127
+}
128
+
129
+func verifyNbf(nbf int64, now int64, required bool) bool {
130
+	if nbf == 0 {
131
+		return !required
132
+	}
133
+	return now >= nbf
134
+}

+ 4 - 0
vendor/github.com/dgrijalva/jwt-go/doc.go

@@ -0,0 +1,4 @@
1
+// Package jwt is a Go implementation of JSON Web Tokens: http://self-issued.info/docs/draft-jones-json-web-token.html
2
+//
3
+// See README.md for more info.
4
+package jwt

+ 147 - 0
vendor/github.com/dgrijalva/jwt-go/ecdsa.go

@@ -0,0 +1,147 @@
1
+package jwt
2
+
3
+import (
4
+	"crypto"
5
+	"crypto/ecdsa"
6
+	"crypto/rand"
7
+	"errors"
8
+	"math/big"
9
+)
10
+
11
+var (
12
+	// Sadly this is missing from crypto/ecdsa compared to crypto/rsa
13
+	ErrECDSAVerification = errors.New("crypto/ecdsa: verification error")
14
+)
15
+
16
+// Implements the ECDSA family of signing methods signing methods
17
+type SigningMethodECDSA struct {
18
+	Name      string
19
+	Hash      crypto.Hash
20
+	KeySize   int
21
+	CurveBits int
22
+}
23
+
24
+// Specific instances for EC256 and company
25
+var (
26
+	SigningMethodES256 *SigningMethodECDSA
27
+	SigningMethodES384 *SigningMethodECDSA
28
+	SigningMethodES512 *SigningMethodECDSA
29
+)
30
+
31
+func init() {
32
+	// ES256
33
+	SigningMethodES256 = &SigningMethodECDSA{"ES256", crypto.SHA256, 32, 256}
34
+	RegisterSigningMethod(SigningMethodES256.Alg(), func() SigningMethod {
35
+		return SigningMethodES256
36
+	})
37
+
38
+	// ES384
39
+	SigningMethodES384 = &SigningMethodECDSA{"ES384", crypto.SHA384, 48, 384}
40
+	RegisterSigningMethod(SigningMethodES384.Alg(), func() SigningMethod {
41
+		return SigningMethodES384
42
+	})
43
+
44
+	// ES512
45
+	SigningMethodES512 = &SigningMethodECDSA{"ES512", crypto.SHA512, 66, 521}
46
+	RegisterSigningMethod(SigningMethodES512.Alg(), func() SigningMethod {
47
+		return SigningMethodES512
48
+	})
49
+}
50
+
51
+func (m *SigningMethodECDSA) Alg() string {
52
+	return m.Name
53
+}
54
+
55
+// Implements the Verify method from SigningMethod
56
+// For this verify method, key must be an ecdsa.PublicKey struct
57
+func (m *SigningMethodECDSA) Verify(signingString, signature string, key interface{}) error {
58
+	var err error
59
+
60
+	// Decode the signature
61
+	var sig []byte
62
+	if sig, err = DecodeSegment(signature); err != nil {
63
+		return err
64
+	}
65
+
66
+	// Get the key
67
+	var ecdsaKey *ecdsa.PublicKey
68
+	switch k := key.(type) {
69
+	case *ecdsa.PublicKey:
70
+		ecdsaKey = k
71
+	default:
72
+		return ErrInvalidKeyType
73
+	}
74
+
75
+	if len(sig) != 2*m.KeySize {
76
+		return ErrECDSAVerification
77
+	}
78
+
79
+	r := big.NewInt(0).SetBytes(sig[:m.KeySize])
80
+	s := big.NewInt(0).SetBytes(sig[m.KeySize:])
81
+
82
+	// Create hasher
83
+	if !m.Hash.Available() {
84
+		return ErrHashUnavailable
85
+	}
86
+	hasher := m.Hash.New()
87
+	hasher.Write([]byte(signingString))
88
+
89
+	// Verify the signature
90
+	if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), r, s); verifystatus == true {
91
+		return nil
92
+	} else {
93
+		return ErrECDSAVerification
94
+	}
95
+}
96
+
97
+// Implements the Sign method from SigningMethod
98
+// For this signing method, key must be an ecdsa.PrivateKey struct
99
+func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string, error) {
100
+	// Get the key
101
+	var ecdsaKey *ecdsa.PrivateKey
102
+	switch k := key.(type) {
103
+	case *ecdsa.PrivateKey:
104
+		ecdsaKey = k
105
+	default:
106
+		return "", ErrInvalidKeyType
107
+	}
108
+
109
+	// Create the hasher
110
+	if !m.Hash.Available() {
111
+		return "", ErrHashUnavailable
112
+	}
113
+
114
+	hasher := m.Hash.New()
115
+	hasher.Write([]byte(signingString))
116
+
117
+	// Sign the string and return r, s
118
+	if r, s, err := ecdsa.Sign(rand.Reader, ecdsaKey, hasher.Sum(nil)); err == nil {
119
+		curveBits := ecdsaKey.Curve.Params().BitSize
120
+
121
+		if m.CurveBits != curveBits {
122
+			return "", ErrInvalidKey
123
+		}
124
+
125
+		keyBytes := curveBits / 8
126
+		if curveBits%8 > 0 {
127
+			keyBytes += 1
128
+		}
129
+
130
+		// We serialize the outpus (r and s) into big-endian byte arrays and pad
131
+		// them with zeros on the left to make sure the sizes work out. Both arrays
132
+		// must be keyBytes long, and the output must be 2*keyBytes long.
133
+		rBytes := r.Bytes()
134
+		rBytesPadded := make([]byte, keyBytes)
135
+		copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)
136
+
137
+		sBytes := s.Bytes()
138
+		sBytesPadded := make([]byte, keyBytes)
139
+		copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)
140
+
141
+		out := append(rBytesPadded, sBytesPadded...)
142
+
143
+		return EncodeSegment(out), nil
144
+	} else {
145
+		return "", err
146
+	}
147
+}

+ 67 - 0
vendor/github.com/dgrijalva/jwt-go/ecdsa_utils.go

@@ -0,0 +1,67 @@
1
+package jwt
2
+
3
+import (
4
+	"crypto/ecdsa"
5
+	"crypto/x509"
6
+	"encoding/pem"
7
+	"errors"
8
+)
9
+
10
+var (
11
+	ErrNotECPublicKey  = errors.New("Key is not a valid ECDSA public key")
12
+	ErrNotECPrivateKey = errors.New("Key is not a valid ECDSA private key")
13
+)
14
+
15
+// Parse PEM encoded Elliptic Curve Private Key Structure
16
+func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) {
17
+	var err error
18
+
19
+	// Parse PEM block
20
+	var block *pem.Block
21
+	if block, _ = pem.Decode(key); block == nil {
22
+		return nil, ErrKeyMustBePEMEncoded
23
+	}
24
+
25
+	// Parse the key
26
+	var parsedKey interface{}
27
+	if parsedKey, err = x509.ParseECPrivateKey(block.Bytes); err != nil {
28
+		return nil, err
29
+	}
30
+
31
+	var pkey *ecdsa.PrivateKey
32
+	var ok bool
33
+	if pkey, ok = parsedKey.(*ecdsa.PrivateKey); !ok {
34
+		return nil, ErrNotECPrivateKey
35
+	}
36
+
37
+	return pkey, nil
38
+}
39
+
40
+// Parse PEM encoded PKCS1 or PKCS8 public key
41
+func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error) {
42
+	var err error
43
+
44
+	// Parse PEM block
45
+	var block *pem.Block
46
+	if block, _ = pem.Decode(key); block == nil {
47
+		return nil, ErrKeyMustBePEMEncoded
48
+	}
49
+
50
+	// Parse the key
51
+	var parsedKey interface{}
52
+	if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
53
+		if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
54
+			parsedKey = cert.PublicKey
55
+		} else {
56
+			return nil, err
57
+		}
58
+	}
59
+
60
+	var pkey *ecdsa.PublicKey
61
+	var ok bool
62
+	if pkey, ok = parsedKey.(*ecdsa.PublicKey); !ok {
63
+		return nil, ErrNotECPublicKey
64
+	}
65
+
66
+	return pkey, nil
67
+}

+ 59 - 0
vendor/github.com/dgrijalva/jwt-go/errors.go

@@ -0,0 +1,59 @@
1
+package jwt
2
+
3
+import (
4
+	"errors"
5
+)
6
+
7
+// Error constants
8
+var (
9
+	ErrInvalidKey      = errors.New("key is invalid")
10
+	ErrInvalidKeyType  = errors.New("key is of invalid type")
11
+	ErrHashUnavailable = errors.New("the requested hash function is unavailable")
12
+)
13
+
14
+// The errors that might occur when parsing and validating a token
15
+const (
16
+	ValidationErrorMalformed        uint32 = 1 << iota // Token is malformed
17
+	ValidationErrorUnverifiable                        // Token could not be verified because of signing problems
18
+	ValidationErrorSignatureInvalid                    // Signature validation failed
19
+
20
+	// Standard Claim validation errors
21
+	ValidationErrorAudience      // AUD validation failed
22
+	ValidationErrorExpired       // EXP validation failed
23
+	ValidationErrorIssuedAt      // IAT validation failed
24
+	ValidationErrorIssuer        // ISS validation failed
25
+	ValidationErrorNotValidYet   // NBF validation failed
26
+	ValidationErrorId            // JTI validation failed
27
+	ValidationErrorClaimsInvalid // Generic claims validation error
28
+)
29
+
30
+// Helper for constructing a ValidationError with a string error message
31
+func NewValidationError(errorText string, errorFlags uint32) *ValidationError {
32
+	return &ValidationError{
33
+		text:   errorText,
34
+		Errors: errorFlags,
35
+	}
36
+}
37
+
38
+// The error from Parse if token is not valid
39
+type ValidationError struct {
40
+	Inner  error  // stores the error returned by external dependencies, i.e.: KeyFunc
41
+	Errors uint32 // bitfield.  see ValidationError... constants
42
+	text   string // errors that do not have a valid error just have text
43
+}
44
+
45
+// Validation error is an error type
46
+func (e ValidationError) Error() string {
47
+	if e.Inner != nil {
48
+		return e.Inner.Error()
49
+	} else if e.text != "" {
50
+		return e.text
51
+	} else {
52
+		return "token is invalid"
53
+	}
54
+}
55
+
56
+// No errors
57
+func (e *ValidationError) valid() bool {
58
+	return e.Errors == 0
59
+}

+ 94 - 0
vendor/github.com/dgrijalva/jwt-go/hmac.go

@@ -0,0 +1,94 @@
1
+package jwt
2
+
3
+import (
4
+	"crypto"
5
+	"crypto/hmac"
6
+	"errors"
7
+)
8
+
9
+// Implements the HMAC-SHA family of signing methods signing methods
10
+type SigningMethodHMAC struct {
11
+	Name string
12
+	Hash crypto.Hash
13
+}
14
+
15
+// Specific instances for HS256 and company
16
+var (
17
+	SigningMethodHS256  *SigningMethodHMAC
18
+	SigningMethodHS384  *SigningMethodHMAC
19
+	SigningMethodHS512  *SigningMethodHMAC
20
+	ErrSignatureInvalid = errors.New("signature is invalid")
21
+)
22
+
23
+func init() {
24
+	// HS256
25
+	SigningMethodHS256 = &SigningMethodHMAC{"HS256", crypto.SHA256}
26
+	RegisterSigningMethod(SigningMethodHS256.Alg(), func() SigningMethod {
27
+		return SigningMethodHS256
28
+	})
29
+
30
+	// HS384
31
+	SigningMethodHS384 = &SigningMethodHMAC{"HS384", crypto.SHA384}
32
+	RegisterSigningMethod(SigningMethodHS384.Alg(), func() SigningMethod {
33
+		return SigningMethodHS384
34
+	})
35
+
36
+	// HS512
37
+	SigningMethodHS512 = &SigningMethodHMAC{"HS512", crypto.SHA512}
38
+	RegisterSigningMethod(SigningMethodHS512.Alg(), func() SigningMethod {
39
+		return SigningMethodHS512
40
+	})
41
+}
42
+
43
+func (m *SigningMethodHMAC) Alg() string {
44
+	return m.Name
45
+}
46
+
47
+// Verify the signature of HSXXX tokens.  Returns nil if the signature is valid.
48
+func (m *SigningMethodHMAC) Verify(signingString, signature string, key interface{}) error {
49
+	// Verify the key is the right type
50
+	keyBytes, ok := key.([]byte)
51
+	if !ok {
52
+		return ErrInvalidKeyType
53
+	}
54
+
55
+	// Decode signature, for comparison
56
+	sig, err := DecodeSegment(signature)
57
+	if err != nil {
58
+		return err
59
+	}
60
+
61
+	// Can we use the specified hashing method?
62
+	if !m.Hash.Available() {
63
+		return ErrHashUnavailable
64
+	}
65
+
66
+	// This signing method is symmetric, so we validate the signature
67
+	// by reproducing the signature from the signing string and key, then
68
+	// comparing that against the provided signature.
69
+	hasher := hmac.New(m.Hash.New, keyBytes)
70
+	hasher.Write([]byte(signingString))
71
+	if !hmac.Equal(sig, hasher.Sum(nil)) {
72
+		return ErrSignatureInvalid
73
+	}
74
+
75
+	// No validation errors.  Signature is good.
76
+	return nil
77
+}
78
+
79
+// Implements the Sign method from SigningMethod for this signing method.
80
+// Key must be []byte
81
+func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string, error) {
82
+	if keyBytes, ok := key.([]byte); ok {
83
+		if !m.Hash.Available() {
84
+			return "", ErrHashUnavailable
85
+		}
86
+
87
+		hasher := hmac.New(m.Hash.New, keyBytes)
88
+		hasher.Write([]byte(signingString))
89
+
90
+		return EncodeSegment(hasher.Sum(nil)), nil
91
+	}
92
+
93
+	return "", ErrInvalidKey
94
+}

+ 94 - 0
vendor/github.com/dgrijalva/jwt-go/map_claims.go

@@ -0,0 +1,94 @@
1
+package jwt
2
+
3
+import (
4
+	"encoding/json"
5
+	"errors"
6
+	// "fmt"
7
+)
8
+
9
+// Claims type that uses the map[string]interface{} for JSON decoding
10
+// This is the default claims type if you don't supply one
11
+type MapClaims map[string]interface{}
12
+
13
+// Compares the aud claim against cmp.
14
+// If required is false, this method will return true if the value matches or is unset
15
+func (m MapClaims) VerifyAudience(cmp string, req bool) bool {
16
+	aud, _ := m["aud"].(string)
17
+	return verifyAud(aud, cmp, req)
18
+}
19
+
20
+// Compares the exp claim against cmp.
21
+// If required is false, this method will return true if the value matches or is unset
22
+func (m MapClaims) VerifyExpiresAt(cmp int64, req bool) bool {
23
+	switch exp := m["exp"].(type) {
24
+	case float64:
25
+		return verifyExp(int64(exp), cmp, req)
26
+	case json.Number:
27
+		v, _ := exp.Int64()
28
+		return verifyExp(v, cmp, req)
29
+	}
30
+	return req == false
31
+}
32
+
33
+// Compares the iat claim against cmp.
34
+// If required is false, this method will return true if the value matches or is unset
35
+func (m MapClaims) VerifyIssuedAt(cmp int64, req bool) bool {
36
+	switch iat := m["iat"].(type) {
37
+	case float64:
38
+		return verifyIat(int64(iat), cmp, req)
39
+	case json.Number:
40
+		v, _ := iat.Int64()
41
+		return verifyIat(v, cmp, req)
42
+	}
43
+	return req == false
44
+}
45
+
46
+// Compares the iss claim against cmp.
47
+// If required is false, this method will return true if the value matches or is unset
48
+func (m MapClaims) VerifyIssuer(cmp string, req bool) bool {
49
+	iss, _ := m["iss"].(string)
50
+	return verifyIss(iss, cmp, req)
51
+}
52
+
53
+// Compares the nbf claim against cmp.
54
+// If required is false, this method will return true if the value matches or is unset
55
+func (m MapClaims) VerifyNotBefore(cmp int64, req bool) bool {
56
+	switch nbf := m["nbf"].(type) {
57
+	case float64:
58
+		return verifyNbf(int64(nbf), cmp, req)
59
+	case json.Number:
60
+		v, _ := nbf.Int64()
61
+		return verifyNbf(v, cmp, req)
62
+	}
63
+	return req == false
64
+}
65
+
66
+// Validates time based claims "exp, iat, nbf".
67
+// There is no accounting for clock skew.
68
+// As well, if any of the above claims are not in the token, it will still
69
+// be considered a valid claim.
70
+func (m MapClaims) Valid() error {
71
+	vErr := new(ValidationError)
72
+	now := TimeFunc().Unix()
73
+
74
+	if m.VerifyExpiresAt(now, false) == false {
75
+		vErr.Inner = errors.New("Token is expired")
76
+		vErr.Errors |= ValidationErrorExpired
77
+	}
78
+
79
+	if m.VerifyIssuedAt(now, false) == false {
80
+		vErr.Inner = errors.New("Token used before issued")
81
+		vErr.Errors |= ValidationErrorIssuedAt
82
+	}
83
+
84
+	if m.VerifyNotBefore(now, false) == false {
85
+		vErr.Inner = errors.New("Token is not valid yet")
86
+		vErr.Errors |= ValidationErrorNotValidYet
87
+	}
88
+
89
+	if vErr.valid() {
90
+		return nil
91
+	}
92
+
93
+	return vErr
94
+}

+ 52 - 0
vendor/github.com/dgrijalva/jwt-go/none.go

@@ -0,0 +1,52 @@
1
+package jwt
2
+
3
+// Implements the none signing method.  This is required by the spec
4
+// but you probably should never use it.
5
+var SigningMethodNone *signingMethodNone
6
+
7
+const UnsafeAllowNoneSignatureType unsafeNoneMagicConstant = "none signing method allowed"
8
+
9
+var NoneSignatureTypeDisallowedError error
10
+
11
+type signingMethodNone struct{}
12
+type unsafeNoneMagicConstant string
13
+
14
+func init() {
15
+	SigningMethodNone = &signingMethodNone{}
16
+	NoneSignatureTypeDisallowedError = NewValidationError("'none' signature type is not allowed", ValidationErrorSignatureInvalid)
17
+
18
+	RegisterSigningMethod(SigningMethodNone.Alg(), func() SigningMethod {
19
+		return SigningMethodNone
20
+	})
21
+}
22
+
23
+func (m *signingMethodNone) Alg() string {
24
+	return "none"
25
+}
26
+
27
+// Only allow 'none' alg type if UnsafeAllowNoneSignatureType is specified as the key
28
+func (m *signingMethodNone) Verify(signingString, signature string, key interface{}) (err error) {
29
+	// Key must be UnsafeAllowNoneSignatureType to prevent accidentally
30
+	// accepting 'none' signing method
31
+	if _, ok := key.(unsafeNoneMagicConstant); !ok {
32
+		return NoneSignatureTypeDisallowedError
33
+	}
34
+	// If signing method is none, signature must be an empty string
35
+	if signature != "" {
36
+		return NewValidationError(
37
+			"'none' signing method with non-empty signature",
38
+			ValidationErrorSignatureInvalid,
39
+		)
40
+	}
41
+
42
+	// Accept 'none' signing method.
43
+	return nil
44
+}
45
+
46
+// Only allow 'none' signing if UnsafeAllowNoneSignatureType is specified as the key
47
+func (m *signingMethodNone) Sign(signingString string, key interface{}) (string, error) {
48
+	if _, ok := key.(unsafeNoneMagicConstant); ok {
49
+		return "", nil
50
+	}
51
+	return "", NoneSignatureTypeDisallowedError
52
+}

+ 131 - 0
vendor/github.com/dgrijalva/jwt-go/parser.go

@@ -0,0 +1,131 @@
1
+package jwt
2
+
3
+import (
4
+	"bytes"
5
+	"encoding/json"
6
+	"fmt"
7
+	"strings"
8
+)
9
+
10
+type Parser struct {
11
+	ValidMethods         []string // If populated, only these methods will be considered valid
12
+	UseJSONNumber        bool     // Use JSON Number format in JSON decoder
13
+	SkipClaimsValidation bool     // Skip claims validation during token parsing
14
+}
15
+
16
+// Parse, validate, and return a token.
17
+// keyFunc will receive the parsed token and should return the key for validating.
18
+// If everything is kosher, err will be nil
19
+func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
20
+	return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc)
21
+}
22
+
23
+func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
24
+	parts := strings.Split(tokenString, ".")
25
+	if len(parts) != 3 {
26
+		return nil, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed)
27
+	}
28
+
29
+	var err error
30
+	token := &Token{Raw: tokenString}
31
+
32
+	// parse Header
33
+	var headerBytes []byte
34
+	if headerBytes, err = DecodeSegment(parts[0]); err != nil {
35
+		if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") {
36
+			return token, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed)
37
+		}
38
+		return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
39
+	}
40
+	if err = json.Unmarshal(headerBytes, &token.Header); err != nil {
41
+		return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
42
+	}
43
+
44
+	// parse Claims
45
+	var claimBytes []byte
46
+	token.Claims = claims
47
+
48
+	if claimBytes, err = DecodeSegment(parts[1]); err != nil {
49
+		return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
50
+	}
51
+	dec := json.NewDecoder(bytes.NewBuffer(claimBytes))
52
+	if p.UseJSONNumber {
53
+		dec.UseNumber()
54
+	}
55
+	// JSON Decode.  Special case for map type to avoid weird pointer behavior
56
+	if c, ok := token.Claims.(MapClaims); ok {
57
+		err = dec.Decode(&c)
58
+	} else {
59
+		err = dec.Decode(&claims)
60
+	}
61
+	// Handle decode error
62
+	if err != nil {
63
+		return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
64
+	}
65
+
66
+	// Lookup signature method
67
+	if method, ok := token.Header["alg"].(string); ok {
68
+		if token.Method = GetSigningMethod(method); token.Method == nil {
69
+			return token, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable)
70
+		}
71
+	} else {
72
+		return token, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable)
73
+	}
74
+
75
+	// Verify signing method is in the required set
76
+	if p.ValidMethods != nil {
77
+		var signingMethodValid = false
78
+		var alg = token.Method.Alg()
79
+		for _, m := range p.ValidMethods {
80
+			if m == alg {
81
+				signingMethodValid = true
82
+				break
83
+			}
84
+		}
85
+		if !signingMethodValid {
86
+			// signing method is not in the listed set
87
+			return token, NewValidationError(fmt.Sprintf("signing method %v is invalid", alg), ValidationErrorSignatureInvalid)
88
+		}
89
+	}
90
+
91
+	// Lookup key
92
+	var key interface{}
93
+	if keyFunc == nil {
94
+		// keyFunc was not provided.  short circuiting validation
95
+		return token, NewValidationError("no Keyfunc was provided.", ValidationErrorUnverifiable)
96
+	}
97
+	if key, err = keyFunc(token); err != nil {
98
+		// keyFunc returned an error
99
+		return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable}
100
+	}
101
+
102
+	vErr := &ValidationError{}
103
+
104
+	// Validate Claims
105
+	if !p.SkipClaimsValidation {
106
+		if err := token.Claims.Valid(); err != nil {
107
+
108
+			// If the Claims Valid returned an error, check if it is a validation error,
109
+			// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set
110
+			if e, ok := err.(*ValidationError); !ok {
111
+				vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
112
+			} else {
113
+				vErr = e
114
+			}
115
+		}
116
+	}
117
+
118
+	// Perform validation
119
+	token.Signature = parts[2]
120
+	if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil {
121
+		vErr.Inner = err
122
+		vErr.Errors |= ValidationErrorSignatureInvalid
123
+	}
124
+
125
+	if vErr.valid() {
126
+		token.Valid = true
127
+		return token, nil
128
+	}
129
+
130
+	return token, vErr
131
+}

+ 100 - 0
vendor/github.com/dgrijalva/jwt-go/rsa.go

@@ -0,0 +1,100 @@
1
+package jwt
2
+
3
+import (
4
+	"crypto"
5
+	"crypto/rand"
6
+	"crypto/rsa"
7
+)
8
+
9
+// Implements the RSA family of signing methods signing methods
10
+type SigningMethodRSA struct {
11
+	Name string
12
+	Hash crypto.Hash
13
+}
14
+
15
+// Specific instances for RS256 and company
16
+var (
17
+	SigningMethodRS256 *SigningMethodRSA
18
+	SigningMethodRS384 *SigningMethodRSA
19
+	SigningMethodRS512 *SigningMethodRSA
20
+)
21
+
22
+func init() {
23
+	// RS256
24
+	SigningMethodRS256 = &SigningMethodRSA{"RS256", crypto.SHA256}
25
+	RegisterSigningMethod(SigningMethodRS256.Alg(), func() SigningMethod {
26
+		return SigningMethodRS256
27
+	})
28
+
29
+	// RS384
30
+	SigningMethodRS384 = &SigningMethodRSA{"RS384", crypto.SHA384}
31
+	RegisterSigningMethod(SigningMethodRS384.Alg(), func() SigningMethod {
32
+		return SigningMethodRS384
33
+	})
34
+
35
+	// RS512
36
+	SigningMethodRS512 = &SigningMethodRSA{"RS512", crypto.SHA512}
37
+	RegisterSigningMethod(SigningMethodRS512.Alg(), func() SigningMethod {
38
+		return SigningMethodRS512
39
+	})
40
+}
41
+
42
+func (m *SigningMethodRSA) Alg() string {
43
+	return m.Name
44
+}
45
+
46
+// Implements the Verify method from SigningMethod
47
+// For this signing method, must be an rsa.PublicKey structure.
48
+func (m *SigningMethodRSA) Verify(signingString, signature string, key interface{}) error {
49
+	var err error
50
+
51
+	// Decode the signature
52
+	var sig []byte
53
+	if sig, err = DecodeSegment(signature); err != nil {
54
+		return err
55
+	}
56
+
57
+	var rsaKey *rsa.PublicKey
58
+	var ok bool
59
+
60
+	if rsaKey, ok = key.(*rsa.PublicKey); !ok {
61
+		return ErrInvalidKeyType
62
+	}
63
+
64
+	// Create hasher
65
+	if !m.Hash.Available() {
66
+		return ErrHashUnavailable
67
+	}
68
+	hasher := m.Hash.New()
69
+	hasher.Write([]byte(signingString))
70
+
71
+	// Verify the signature
72
+	return rsa.VerifyPKCS1v15(rsaKey, m.Hash, hasher.Sum(nil), sig)
73
+}
74
+
75
+// Implements the Sign method from SigningMethod
76
+// For this signing method, must be an rsa.PrivateKey structure.
77
+func (m *SigningMethodRSA) Sign(signingString string, key interface{}) (string, error) {
78
+	var rsaKey *rsa.PrivateKey
79
+	var ok bool
80
+
81
+	// Validate type of key
82
+	if rsaKey, ok = key.(*rsa.PrivateKey); !ok {
83
+		return "", ErrInvalidKey
84
+	}
85
+
86
+	// Create the hasher
87
+	if !m.Hash.Available() {
88
+		return "", ErrHashUnavailable
89
+	}
90
+
91
+	hasher := m.Hash.New()
92
+	hasher.Write([]byte(signingString))
93
+
94
+	// Sign the string and return the encoded bytes
95
+	if sigBytes, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil)); err == nil {
96
+		return EncodeSegment(sigBytes), nil
97
+	} else {
98
+		return "", err
99
+	}
100
+}

+ 126 - 0
vendor/github.com/dgrijalva/jwt-go/rsa_pss.go

@@ -0,0 +1,126 @@
1
+// +build go1.4
2
+
3
+package jwt
4
+
5
+import (
6
+	"crypto"
7
+	"crypto/rand"
8
+	"crypto/rsa"
9
+)
10
+
11
+// Implements the RSAPSS family of signing methods signing methods
12
+type SigningMethodRSAPSS struct {
13
+	*SigningMethodRSA
14
+	Options *rsa.PSSOptions
15
+}
16
+
17
+// Specific instances for RS/PS and company
18
+var (
19
+	SigningMethodPS256 *SigningMethodRSAPSS
20
+	SigningMethodPS384 *SigningMethodRSAPSS
21
+	SigningMethodPS512 *SigningMethodRSAPSS
22
+)
23
+
24
+func init() {
25
+	// PS256
26
+	SigningMethodPS256 = &SigningMethodRSAPSS{
27
+		&SigningMethodRSA{
28
+			Name: "PS256",
29
+			Hash: crypto.SHA256,
30
+		},
31
+		&rsa.PSSOptions{
32
+			SaltLength: rsa.PSSSaltLengthAuto,
33
+			Hash:       crypto.SHA256,
34
+		},
35
+	}
36
+	RegisterSigningMethod(SigningMethodPS256.Alg(), func() SigningMethod {
37
+		return SigningMethodPS256
38
+	})
39
+
40
+	// PS384
41
+	SigningMethodPS384 = &SigningMethodRSAPSS{
42
+		&SigningMethodRSA{
43
+			Name: "PS384",
44
+			Hash: crypto.SHA384,
45
+		},
46
+		&rsa.PSSOptions{
47
+			SaltLength: rsa.PSSSaltLengthAuto,
48
+			Hash:       crypto.SHA384,
49
+		},
50
+	}
51
+	RegisterSigningMethod(SigningMethodPS384.Alg(), func() SigningMethod {
52
+		return SigningMethodPS384
53
+	})
54
+
55
+	// PS512
56
+	SigningMethodPS512 = &SigningMethodRSAPSS{
57
+		&SigningMethodRSA{
58
+			Name: "PS512",
59
+			Hash: crypto.SHA512,
60
+		},
61
+		&rsa.PSSOptions{
62
+			SaltLength: rsa.PSSSaltLengthAuto,
63
+			Hash:       crypto.SHA512,
64
+		},
65
+	}
66
+	RegisterSigningMethod(SigningMethodPS512.Alg(), func() SigningMethod {
67
+		return SigningMethodPS512
68
+	})
69
+}
70
+
71
+// Implements the Verify method from SigningMethod
72
+// For this verify method, key must be an rsa.PublicKey struct
73
+func (m *SigningMethodRSAPSS) Verify(signingString, signature string, key interface{}) error {
74
+	var err error
75
+
76
+	// Decode the signature
77
+	var sig []byte
78
+	if sig, err = DecodeSegment(signature); err != nil {
79
+		return err
80
+	}
81
+
82
+	var rsaKey *rsa.PublicKey
83
+	switch k := key.(type) {
84
+	case *rsa.PublicKey:
85
+		rsaKey = k
86
+	default:
87
+		return ErrInvalidKey
88
+	}
89
+
90
+	// Create hasher
91
+	if !m.Hash.Available() {
92
+		return ErrHashUnavailable
93
+	}
94
+	hasher := m.Hash.New()
95
+	hasher.Write([]byte(signingString))
96
+
97
+	return rsa.VerifyPSS(rsaKey, m.Hash, hasher.Sum(nil), sig, m.Options)
98
+}
99
+
100
+// Implements the Sign method from SigningMethod
101
+// For this signing method, key must be an rsa.PrivateKey struct
102
+func (m *SigningMethodRSAPSS) Sign(signingString string, key interface{}) (string, error) {
103
+	var rsaKey *rsa.PrivateKey
104
+
105
+	switch k := key.(type) {
106
+	case *rsa.PrivateKey:
107
+		rsaKey = k
108
+	default:
109
+		return "", ErrInvalidKeyType
110
+	}
111
+
112
+	// Create the hasher
113
+	if !m.Hash.Available() {
114
+		return "", ErrHashUnavailable
115
+	}
116
+
117
+	hasher := m.Hash.New()
118
+	hasher.Write([]byte(signingString))
119
+
120
+	// Sign the string and return the encoded bytes
121
+	if sigBytes, err := rsa.SignPSS(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil), m.Options); err == nil {
122
+		return EncodeSegment(sigBytes), nil
123
+	} else {
124
+		return "", err
125
+	}
126
+}

+ 69 - 0
vendor/github.com/dgrijalva/jwt-go/rsa_utils.go

@@ -0,0 +1,69 @@
1
+package jwt
2
+
3
+import (
4
+	"crypto/rsa"
5
+	"crypto/x509"
6
+	"encoding/pem"
7
+	"errors"
8
+)
9
+
10
+var (
11
+	ErrKeyMustBePEMEncoded = errors.New("Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key")
12
+	ErrNotRSAPrivateKey    = errors.New("Key is not a valid RSA private key")
13
+	ErrNotRSAPublicKey     = errors.New("Key is not a valid RSA public key")
14
+)
15
+
16
+// Parse PEM encoded PKCS1 or PKCS8 private key
17
+func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) {
18
+	var err error
19
+
20
+	// Parse PEM block
21
+	var block *pem.Block
22
+	if block, _ = pem.Decode(key); block == nil {
23
+		return nil, ErrKeyMustBePEMEncoded
24
+	}
25
+
26
+	var parsedKey interface{}
27
+	if parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
28
+		if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
29
+			return nil, err
30
+		}
31
+	}
32
+
33
+	var pkey *rsa.PrivateKey
34
+	var ok bool
35
+	if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok {
36
+		return nil, ErrNotRSAPrivateKey
37
+	}
38
+
39
+	return pkey, nil
40
+}
41
+
42
+// Parse PEM encoded PKCS1 or PKCS8 public key
43
+func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) {
44
+	var err error
45
+
46
+	// Parse PEM block
47
+	var block *pem.Block
48
+	if block, _ = pem.Decode(key); block == nil {
49
+		return nil, ErrKeyMustBePEMEncoded
50
+	}
51
+
52
+	// Parse the key
53
+	var parsedKey interface{}
54
+	if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
55
+		if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
56
+			parsedKey = cert.PublicKey
57
+		} else {
58
+			return nil, err
59
+		}
60
+	}
61
+
62
+	var pkey *rsa.PublicKey
63
+	var ok bool
64
+	if pkey, ok = parsedKey.(*rsa.PublicKey); !ok {
65
+		return nil, ErrNotRSAPublicKey
66
+	}
67
+
68
+	return pkey, nil
69
+}

+ 35 - 0
vendor/github.com/dgrijalva/jwt-go/signing_method.go

@@ -0,0 +1,35 @@
1
+package jwt
2
+
3
+import (
4
+	"sync"
5
+)
6
+
7
+var signingMethods = map[string]func() SigningMethod{}
8
+var signingMethodLock = new(sync.RWMutex)
9
+
10
+// Implement SigningMethod to add new methods for signing or verifying tokens.
11
+type SigningMethod interface {
12
+	Verify(signingString, signature string, key interface{}) error // Returns nil if signature is valid
13
+	Sign(signingString string, key interface{}) (string, error)    // Returns encoded signature or error
14
+	Alg() string                                                   // returns the alg identifier for this method (example: 'HS256')
15
+}
16
+
17
+// Register the "alg" name and a factory function for signing method.
18
+// This is typically done during init() in the method's implementation
19
+func RegisterSigningMethod(alg string, f func() SigningMethod) {
20
+	signingMethodLock.Lock()
21
+	defer signingMethodLock.Unlock()
22
+
23
+	signingMethods[alg] = f
24
+}
25
+
26
+// Get a signing method from an "alg" string
27
+func GetSigningMethod(alg string) (method SigningMethod) {
28
+	signingMethodLock.RLock()
29
+	defer signingMethodLock.RUnlock()
30
+
31
+	if methodF, ok := signingMethods[alg]; ok {
32
+		method = methodF()
33
+	}
34
+	return
35
+}

+ 108 - 0
vendor/github.com/dgrijalva/jwt-go/token.go

@@ -0,0 +1,108 @@
1
+package jwt
2
+
3
+import (
4
+	"encoding/base64"
5
+	"encoding/json"
6
+	"strings"
7
+	"time"
8
+)
9
+
10
+// TimeFunc provides the current time when parsing token to validate "exp" claim (expiration time).
11
+// You can override it to use another time value.  This is useful for testing or if your
12
+// server uses a different time zone than your tokens.
13
+var TimeFunc = time.Now
14
+
15
+// Parse methods use this callback function to supply
16
+// the key for verification.  The function receives the parsed,
17
+// but unverified Token.  This allows you to use properties in the
18
+// Header of the token (such as `kid`) to identify which key to use.
19
+type Keyfunc func(*Token) (interface{}, error)
20
+
21
+// A JWT Token.  Different fields will be used depending on whether you're
22
+// creating or parsing/verifying a token.
23
+type Token struct {
24
+	Raw       string                 // The raw token.  Populated when you Parse a token
25
+	Method    SigningMethod          // The signing method used or to be used
26
+	Header    map[string]interface{} // The first segment of the token
27
+	Claims    Claims                 // The second segment of the token
28
+	Signature string                 // The third segment of the token.  Populated when you Parse a token
29
+	Valid     bool                   // Is the token valid?  Populated when you Parse/Verify a token
30
+}
31
+
32
+// Create a new Token.  Takes a signing method
33
+func New(method SigningMethod) *Token {
34
+	return NewWithClaims(method, MapClaims{})
35
+}
36
+
37
+func NewWithClaims(method SigningMethod, claims Claims) *Token {
38
+	return &Token{
39
+		Header: map[string]interface{}{
40
+			"typ": "JWT",
41
+			"alg": method.Alg(),
42
+		},
43
+		Claims: claims,
44
+		Method: method,
45
+	}
46
+}
47
+
48
+// Get the complete, signed token
49
+func (t *Token) SignedString(key interface{}) (string, error) {
50
+	var sig, sstr string
51
+	var err error
52
+	if sstr, err = t.SigningString(); err != nil {
53
+		return "", err
54
+	}
55
+	if sig, err = t.Method.Sign(sstr, key); err != nil {
56