lfs: implement HTTP routes (#6035)

* Bootstrap with GORM

* Fix lint error

* Set conn max lifetime to one minute

* Fallback to use gorm v1

* Define HTTP routes

* Finish authentication

* Save token updated

* Add docstring

* Finish authorization

* serveBatch rundown

* Define types in lfsutil

* Finish Batch

* authutil

* Finish basic

* Formalize response error

* Fix lint errors

* authutil: add tests

* dbutil: add tests

* lfsutil: add tests

* strutil: add tests

* Formalize 401 response
This commit is contained in:
ᴜɴᴋɴᴡᴏɴ 2020-04-04 21:14:15 +08:00 committed by GitHub
parent 2bd9d0b9c8
commit 34145c990d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 1847 additions and 558 deletions

View File

@ -14,6 +14,8 @@ jobs:
- uses: actions/checkout@v2
- name: Run golangci-lint
uses: actions-contrib/golangci-lint@v1
with:
args: 'run --timeout=30m'
test:
name: Test

View File

@ -146,6 +146,10 @@ PASSWORD =
SSL_MODE = disable
; For "sqlite3" only, make sure to use absolute path.
PATH = data/gogs.db
; The maximum open connections of the pool.
MAX_OPEN_CONNS = 30
; The maximum idle connections of the pool.
MAX_IDLE_CONNS = 30
[security]
; Whether to show the install page, set this to "true" to bypass it.
@ -259,6 +263,10 @@ HOST =
; The value for "Access-Control-Allow-Origin" header, default is not to present.
ACCESS_CONTROL_ALLOW_ORIGIN =
[lfs]
; The root path to store LFS objects.
OBJECTS_PATH = data/lfs-objects
[attachment]
; Whether to enabled upload attachments in general.
ENABLED = true
@ -391,6 +399,16 @@ MAX_SIZE = 100
; Maximum days to keep logger files
MAX_DAYS = 3
[log.gorm]
; Whether to enable file rotation.
ROTATE = true
; Whether to rotate file every day.
ROTATE_DAILY = true
; The maximum file size in MB before next rotate.
MAX_SIZE = 100
; The maximum days to keep files.
MAX_DAYS = 3
[cron]
; Enable running cron tasks periodically.
ENABLED = true

6
go.mod
View File

@ -4,7 +4,7 @@ go 1.13
require (
github.com/bgentry/speakeasy v0.1.0 // indirect
github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0
github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e
github.com/editorconfig/editorconfig-core-go/v2 v2.3.1
github.com/fatih/color v1.9.0 // indirect
github.com/go-macaron/binding v1.1.0
@ -13,6 +13,7 @@ require (
github.com/go-macaron/csrf v0.0.0-20190812063352-946f6d303a4c
github.com/go-macaron/gzip v0.0.0-20160222043647-cad1c6580a07
github.com/go-macaron/i18n v0.5.0
github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191
github.com/go-macaron/session v0.0.0-20190805070824-1a3cdc6f5659
github.com/go-macaron/toolbox v0.0.0-20190813233741-94defb8383c6
github.com/go-sql-driver/mysql v1.5.0
@ -27,13 +28,14 @@ require (
github.com/google/go-querystring v1.0.0 // indirect
github.com/issue9/identicon v1.0.1
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43
github.com/jinzhu/gorm v1.9.12
github.com/json-iterator/go v1.1.9
github.com/klauspost/compress v1.8.6 // indirect
github.com/klauspost/cpuid v1.2.1 // indirect
github.com/lib/pq v1.3.0
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/mattn/go-runewidth v0.0.4 // indirect
github.com/mattn/go-sqlite3 v1.13.0
github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2
github.com/microcosm-cc/bluemonday v1.0.2
github.com/msteinert/pam v0.0.0-20190215180659-f29b9f28d6f9

119
go.sum
View File

@ -4,12 +4,6 @@ cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7h
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
github.com/alecthomas/chroma v0.7.1 h1:G1i02OhUbRi2nJxcNkwJaY/J1gHXj9tt72qN6ZouLFQ=
github.com/alecthomas/chroma v0.7.1/go.mod h1:gHw09mkX1Qp80JlYbmN9L3+4R5o6DJJ3GRShh+AICNc=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -19,16 +13,11 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 h1:U/lr3Dgy4WK+hNk4tyD+nuGjpVLPEHuJSFXMw11/HPA=
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
@ -38,27 +27,20 @@ github.com/couchbaselabs/go-couchbase v0.0.0-20190708161019-23e7ca2ce2b7/go.mod
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0 h1:epsH3lb7KVbXHYk7LYGN5EiE0MxcevHU85CKITJ0wUY=
github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dlclark/regexp2 v1.1.6 h1:CqB4MjHw0MFCDj+PHHjiESmHX+N7t0tJzKvC6M97BRg=
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e h1:LzwWXEScfcTu7vUZNlDDWDARoSGEtvlDKK2BYHowNeE=
github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/editorconfig/editorconfig-core-go/v2 v2.2.1 h1:jY5PCRQf4V0oqpim/Ympl6MwHcb9+nBHEnHOPXqNZ/A=
github.com/editorconfig/editorconfig-core-go/v2 v2.2.1/go.mod h1:6XDmqAZsQu8ikS+onLRJfLZvTP3RWTVT8ROX6qcdkio=
github.com/editorconfig/editorconfig-core-go/v2 v2.3.0 h1:QD1YB/rbntMEQIKM42kQOaqGdS13UvGsl9c8m/nFNWY=
github.com/editorconfig/editorconfig-core-go/v2 v2.3.0/go.mod h1:RNdPfKd9PliYEUZ3r+GxbDsSHNnEluC1wdkQJc3jD4k=
github.com/editorconfig/editorconfig-core-go/v2 v2.3.1 h1:8+L7G4cCtuYprGaNawfTBq20m8+VpPCH2O0vwKS7r84=
github.com/editorconfig/editorconfig-core-go/v2 v2.3.1/go.mod h1:mJYZ8yC2PWr+pabYXwHMfcEe45fh2w2sxk8cudJdLPM=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
@ -67,8 +49,6 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-macaron/binding v1.0.1 h1:4LASxd4EKsESZ6ZMyzNVX+TM4Yuex4bTHYyz/PQjsRA=
github.com/go-macaron/binding v1.0.1/go.mod h1:AG8Z6qkQM8s47aUDJOco/SNwJ8Czif2hMm7rc0abDog=
github.com/go-macaron/binding v1.1.0 h1:A5jpr5UdHr81Hfmb6QUAMTHyvniudOMcgtEg13TJ1ig=
github.com/go-macaron/binding v1.1.0/go.mod h1:dJU/AtPKG0gUiFra1K5TTGduFGMNxMvfJzV/zmXwyGM=
github.com/go-macaron/cache v0.0.0-20190810181446-10f7c57e2196 h1:fqWZxyMLF6RVGmjvsZ9FijiU9UlAjuE6nu9RfNBZ+iE=
@ -87,12 +67,10 @@ github.com/go-macaron/session v0.0.0-20190805070824-1a3cdc6f5659 h1:YXDFNK98PgKe
github.com/go-macaron/session v0.0.0-20190805070824-1a3cdc6f5659/go.mod h1:tLd0QEudXocQckwcpCq5pCuTCuYc24I0bRJDuRe9OuQ=
github.com/go-macaron/toolbox v0.0.0-20190813233741-94defb8383c6 h1:x/v1iUWlqXTKVg17ulB0qCgcM2s+eysAbr/dseKLLss=
github.com/go-macaron/toolbox v0.0.0-20190813233741-94defb8383c6/go.mod h1:YFNJ/JT4yLnpuIXTFef30SZkxGHUczjGZGFaZpPcdn0=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@ -100,25 +78,12 @@ github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561 h1:aBzukfDxQlCTVS0NBU
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 h1:yXtpJr/LV6PFu4nTLgfjQdcMdzjbqqXMEnHfq0Or6p8=
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14/go.mod h1:jPoNZLWDAqA5N3G5amEoiNbhVrmM+ZQEcnQvNQ2KaZk=
github.com/gogs/git-module v0.8.3 h1:9f8oxSs9OACWrGBYMVnnQNzyTcVN+zzcBM7CXnbmezw=
github.com/gogs/git-module v0.8.3/go.mod h1:aj4tcm7DxaszJWpZLZIRL6gfPXyguAHiE1PDfAAPrCw=
github.com/gogs/git-module v1.0.0-beta.4 h1:5CyCvTfrb2n5LRpHcNIaFnywHDkM/NxSZVP6t4tpTXI=
github.com/gogs/git-module v1.0.0-beta.4/go.mod h1:oN37FFStFjdnTJXsSbhIHKJXh2YeDsEcXPATVz/oeuQ=
github.com/gogs/git-module v1.0.0 h1:iOlCZ5kPc3RjnWRxdziL5hjCaosYyZw/Lf2odzR/kjw=
github.com/gogs/git-module v1.0.0/go.mod h1:oN37FFStFjdnTJXsSbhIHKJXh2YeDsEcXPATVz/oeuQ=
github.com/gogs/git-module v1.0.1 h1:Xh/sfk6zKjF3y9w2G/dN0YMfLjMhRQzqxMTUPHOL5n4=
github.com/gogs/git-module v1.0.1/go.mod h1:oN37FFStFjdnTJXsSbhIHKJXh2YeDsEcXPATVz/oeuQ=
github.com/gogs/git-module v1.0.2 h1:YrDZV4g489A4sOF3+gQq85UnVBjLn30+w3PF5PBoGpQ=
github.com/gogs/git-module v1.0.2/go.mod h1:oN37FFStFjdnTJXsSbhIHKJXh2YeDsEcXPATVz/oeuQ=
github.com/gogs/git-module v1.1.0 h1:OEQAWvhZ4TCsq6Vw/ftyA37Os1QkiPu1uMQpF6ErzG0=
github.com/gogs/git-module v1.1.0/go.mod h1:oN37FFStFjdnTJXsSbhIHKJXh2YeDsEcXPATVz/oeuQ=
github.com/gogs/git-module v1.1.1 h1:/taoHtOHLorlmQJ7zLBQvJGGgM9LRIoGGH1et4Upzvo=
github.com/gogs/git-module v1.1.1/go.mod h1:oN37FFStFjdnTJXsSbhIHKJXh2YeDsEcXPATVz/oeuQ=
github.com/gogs/go-gogs-client v0.0.0-20200128182646-c69cb7680fd4 h1:C7NryI/RQhsIWwC2bHN601P1wJKeuQ6U/UCOYTn3Cic=
github.com/gogs/go-gogs-client v0.0.0-20200128182646-c69cb7680fd4/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
github.com/gogs/go-libravatar v0.0.0-20191106065024-33a75213d0a0 h1:K02vod+sn3M1OOkdqi2tPxN2+xESK4qyITVQ3JkGEv4=
github.com/gogs/go-libravatar v0.0.0-20191106065024-33a75213d0a0/go.mod h1:Zas3BtO88pk1cwUfEYlvnl/CRwh0ybDxRWSwRjG8I3w=
github.com/gogs/minwinsvc v0.0.0-20170301035411-95be6356811a h1:8DZwxETOVWIinYxDK+i6L+rMb7eGATGaakD6ZucfHVk=
github.com/gogs/minwinsvc v0.0.0-20170301035411-95be6356811a/go.mod h1:TUIZ+29jodWQ8Gk6Pvtg4E09aMsc3C/VLZiVYfUhWQU=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
@ -133,10 +98,7 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8l
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
@ -148,26 +110,26 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/issue9/assert v1.3.1 h1:L8pRpbnzMIPFJqrMKR/oG03uWrtVeZyYBpI2U2Jx1JE=
github.com/issue9/assert v1.3.1/go.mod h1:9Ger+iz8X7r1zMYYwEhh++2wMGWcNN2oVI+zIQXxcio=
github.com/issue9/identicon v1.0.1 h1:pCDfjMDM6xWK0Chxo8Lif+ST/nOEtmXgMITgV1YA9Og=
github.com/issue9/identicon v1.0.1/go.mod h1:UKNVkUFI68RPz/RlLhsAr1aX6bBSaYEWRHVfdjrMUmk=
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43 h1:jTkyeF7NZ5oIr0ESmcrpiDgAfoidCBF4F5kJhjtaRwE=
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q=
github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@ -177,24 +139,19 @@ github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ=
github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af/go.mod h1:Cqz6pqow14VObJ7peltM+2n3PWOz7yTrfUuGbVFkzN0=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
@ -202,10 +159,10 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c=
github.com/mattn/go-sqlite3 v1.13.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
@ -213,20 +170,16 @@ github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 h1:YocNLcTBdEd
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/msteinert/pam v0.0.0-20190215180659-f29b9f28d6f9 h1:ZivaaKmjs9q90zi6I4gTLW6tbVGtlBjellr3hMYaly0=
github.com/msteinert/pam v0.0.0-20190215180659-f29b9f28d6f9/go.mod h1:np1wUFZ6tyoke22qDJZY40URn9Ae51gX7ljIWXN5TJs=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/niklasfasching/go-org v0.1.6 h1:F521WcqRNl8OJumlgAnekZgERaTA2HpfOYYfVEKOeI8=
github.com/niklasfasching/go-org v0.1.6/go.mod h1:AsLD6X7djzRIz4/RFZu8vwRL0VGjUvGZCCH1Nz0VdrU=
github.com/niklasfasching/go-org v0.1.9 h1:Toz8WMIt+qJb52uYEk1YD/muLuOOmRt1CfkV+bKVMkI=
github.com/niklasfasching/go-org v0.1.9/go.mod h1:AsLD6X7djzRIz4/RFZu8vwRL0VGjUvGZCCH1Nz0VdrU=
github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
@ -240,7 +193,6 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -251,28 +203,20 @@ github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI=
github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=
github.com/prometheus/client_golang v1.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA=
github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
@ -280,12 +224,9 @@ github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNue
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
@ -298,20 +239,16 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@ -325,13 +262,8 @@ github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6 h1:sRrkJEHtNoaSvyXMbR
github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ=
github.com/unknwon/paginater v0.0.0-20170405233947-45e5d631308e h1:Qf3QQl/zmEbWDajFEiisbKN83hLY+eq2MhbA0I1/two=
github.com/unknwon/paginater v0.0.0-20170405233947-45e5d631308e/go.mod h1:TBwoao3Q4Eb/cp+dHbXDfRTrZSsj/k7kLr2j1oWRWC0=
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.3 h1:FpNT6zq26xNpHZy08emi755QwzLPs6Pukqjlc7RfOMU=
github.com/urfave/cli v1.22.3/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -340,8 +272,7 @@ golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 h1:Sy5bstxEqwwbYs6n0/pBuxKENqOeZUgD45Gp3Q3pqLg=
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -379,7 +310,6 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -387,8 +317,6 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -416,7 +344,6 @@ google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMt
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -424,33 +351,23 @@ google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRn
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e h1:wGA78yza6bu/mWcc4QfBuIEHEtc06xdiU0X8sY36yUU=
gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e/go.mod h1:xsQCaysVCudhrYTfzYWe577fCe7Ceci+6qjO2Rdc0Z4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.52.0 h1:j+Lt/M1oPPejkniCg1TkWE2J3Eh1oZTsHSXzMTzUXn4=
gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.53.0 h1:c7ruDvTQi0MUTFuNpDRXLSjs7xT4TerM1icIg4uKWRg=
gopkg.in/ini.v1 v1.53.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.54.0 h1:oM5ElzbIi7gwLnNbPX2M25ED1vSAK3B6dex50eS/6Fs=
gopkg.in/ini.v1 v1.54.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ=
gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU=
gopkg.in/ldap.v2 v2.5.1/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk=
gopkg.in/macaron.v1 v1.3.4 h1:HvIscOwxhFhx3swWM/979wh2QMYyuXrNmrF9l+j3HZs=
gopkg.in/macaron.v1 v1.3.4/go.mod h1:/RoHTdC8ALpyJ3+QR36mKjwnT1F1dyYtsGM9Ate6ZFI=
gopkg.in/macaron.v1 v1.3.5 h1:FUA16VFBojxzfU75KqWrV/6BPv9O2R1GnybSGRie9QQ=
gopkg.in/macaron.v1 v1.3.5/go.mod h1:uMZCFccv9yr5TipIalVOyAyZQuOH3OkmXvgcWwhJuP4=
@ -458,19 +375,13 @@ gopkg.in/redis.v2 v2.3.2 h1:GPVIIB/JnL1wvfULefy3qXmPu1nfNu2d0yA09FHgwfs=
gopkg.in/redis.v2 v2.3.2/go.mod h1:4wl9PJ/CqzeHk3LVq1hNLHH8krm3+AXEgut4jVc++LU=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
unknwon.dev/clog/v2 v2.1.0 h1:4iXteBnL9ESvxoNkiaWx4UTU6yEgEmDxFaMANyiq4b8=
unknwon.dev/clog/v2 v2.1.0/go.mod h1:zvUlyibDHI4mykYdWyWje2G9nF/nBzfDOqRo2my4mWc=
unknwon.dev/clog/v2 v2.1.1 h1:jBmBoMfsedJ/Sirm4/TdDy00mxh1vlbr9dM+AnYsNik=
unknwon.dev/clog/v2 v2.1.1/go.mod h1:zvUlyibDHI4mykYdWyWje2G9nF/nBzfDOqRo2my4mWc=
unknwon.dev/clog/v2 v2.1.2 h1:+jwPPp10UtOPunFtviUmXF01Abf6q7p5GEy4jluLl8o=
unknwon.dev/clog/v2 v2.1.2/go.mod h1:zvUlyibDHI4mykYdWyWje2G9nF/nBzfDOqRo2my4mWc=
xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8=

File diff suppressed because one or more lines are too long

View File

@ -19922,7 +19922,7 @@ func jsGogsJs() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "js/gogs.js", size: 47683, mode: os.FileMode(0644), modTime: time.Unix(1585391597, 0)}
info := bindataFileInfo{name: "js/gogs.js", size: 47683, mode: os.FileMode(0644), modTime: time.Unix(1585394288, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x45, 0x65, 0x98, 0x95, 0x98, 0xb5, 0x1, 0xee, 0x42, 0xf2, 0x3a, 0xc2, 0x75, 0xd2, 0x72, 0x86, 0xb7, 0xa3, 0x83, 0x6c, 0x24, 0x8f, 0x7, 0x22, 0x8c, 0xc3, 0xea, 0x4e, 0x7a, 0x74, 0x64, 0xd8}}
return a, nil
}

View File

@ -48,18 +48,18 @@ func SignedInID(c *macaron.Context, sess session.Store) (_ int64, isTokenAuth bo
// Let's see if token is valid.
if len(tokenSHA) > 0 {
t, err := db.GetAccessTokenBySHA(tokenSHA)
t, err := db.AccessTokens.GetBySHA(tokenSHA)
if err != nil {
if !db.IsErrAccessTokenNotExist(err) && !db.IsErrAccessTokenEmpty(err) {
if !db.IsErrAccessTokenNotExist(err) {
log.Error("GetAccessTokenBySHA: %v", err)
}
return 0, false
}
t.Updated = time.Now()
if err = db.UpdateAccessToken(t); err != nil {
if err = db.AccessTokens.Save(t); err != nil {
log.Error("UpdateAccessToken: %v", err)
}
return t.UID, true
return t.UserID, true
}
}
@ -90,7 +90,7 @@ func SignedInUser(ctx *macaron.Context, sess session.Store) (_ *db.User, isBasic
if uid <= 0 {
if conf.Auth.EnableReverseProxyAuthentication {
webAuthUser := ctx.Req.Header.Get(conf.Security.ReverseProxyAuthenticationUser)
webAuthUser := ctx.Req.Header.Get(conf.Auth.ReverseProxyAuthenticationHeader)
if len(webAuthUser) > 0 {
u, err := db.GetUserByName(webAuthUser)
if err != nil {
@ -127,7 +127,7 @@ func SignedInUser(ctx *macaron.Context, sess session.Store) (_ *db.User, isBasic
if len(auths) == 2 && auths[0] == "Basic" {
uname, passwd, _ := tool.BasicAuthDecode(auths[1])
u, err := db.UserLogin(uname, passwd, -1)
u, err := db.Users.Authenticate(uname, passwd, -1)
if err != nil {
if !db.IsErrUserNotExist(err) {
log.Error("Failed to authenticate user: %v", err)

View File

@ -0,0 +1,35 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package authutil
import (
"encoding/base64"
"net/http"
"strings"
)
// DecodeBasic extracts username and password from given header using HTTP Basic Auth.
// It returns empty strings if values are not presented or not valid.
func DecodeBasic(header http.Header) (username, password string) {
if len(header) == 0 {
return "", ""
}
fields := strings.Fields(header.Get("Authorization"))
if len(fields) != 2 || fields[0] != "Basic" {
return "", ""
}
p, err := base64.StdEncoding.DecodeString(fields[1])
if err != nil {
return "", ""
}
creds := strings.SplitN(string(p), ":", 2)
if len(creds) == 1 {
return creds[0], ""
}
return creds[0], creds[1]
}

View File

@ -0,0 +1,72 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package authutil
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
func TestDecodeBasic(t *testing.T) {
tests := []struct {
name string
header http.Header
expUsername string
expPassword string
}{
{
name: "no header",
},
{
name: "no authorization",
header: http.Header{
"Content-Type": []string{"text/plain"},
},
},
{
name: "malformed value",
header: http.Header{
"Authorization": []string{"Basic"},
},
},
{
name: "not basic",
header: http.Header{
"Authorization": []string{"Digest dummy"},
},
},
{
name: "bad encoding",
header: http.Header{
"Authorization": []string{"Basic not_base64"},
},
},
{
name: "only has username",
header: http.Header{
"Authorization": []string{"Basic dXNlcm5hbWU="},
},
expUsername: "username",
},
{
name: "has username and password",
header: http.Header{
"Authorization": []string{"Basic dXNlcm5hbWU6cGFzc3dvcmQ="},
},
expUsername: "username",
expPassword: "password",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
username, password := DecodeBasic(test.header)
assert.Equal(t, test.expUsername, username)
assert.Equal(t, test.expPassword, password)
})
}
}

View File

@ -126,9 +126,9 @@ func checkDeployKey(key *db.PublicKey, repo *db.Repository) {
var (
allowedCommands = map[string]db.AccessMode{
"git-upload-pack": db.ACCESS_MODE_READ,
"git-upload-archive": db.ACCESS_MODE_READ,
"git-receive-pack": db.ACCESS_MODE_WRITE,
"git-upload-pack": db.AccessModeRead,
"git-upload-archive": db.AccessModeRead,
"git-receive-pack": db.AccessModeWrite,
}
)
@ -184,7 +184,7 @@ func runServ(c *cli.Context) error {
}
// Prohibit push to mirror repositories.
if requestMode > db.ACCESS_MODE_READ && repo.IsMirror {
if requestMode > db.AccessModeRead && repo.IsMirror {
fail("Mirror repository is read-only", "")
}
@ -196,7 +196,7 @@ func runServ(c *cli.Context) error {
fail("Invalid key ID", "Invalid key ID '%s': %v", c.Args()[0], err)
}
if requestMode == db.ACCESS_MODE_WRITE || repo.IsPrivate {
if requestMode == db.AccessModeWrite || repo.IsPrivate {
// Check deploy key or user key.
if key.IsDeployKey() {
if key.Mode < requestMode {
@ -216,7 +216,7 @@ func runServ(c *cli.Context) error {
if mode < requestMode {
clientMessage := _ACCESS_DENIED_MESSAGE
if mode >= db.ACCESS_MODE_READ {
if mode >= db.AccessModeRead {
clientMessage = "You do not have sufficient authorization for this action"
}
fail(clientMessage,
@ -259,7 +259,7 @@ func runServ(c *cli.Context) error {
} else {
gitCmd = exec.Command(verb, repoFullName)
}
if requestMode == db.ACCESS_MODE_WRITE {
if requestMode == db.AccessModeWrite {
gitCmd.Env = append(os.Environ(), db.ComposeHookEnvs(db.ComposeHookEnvsOptions{
AuthUser: user,
OwnerName: owner.Name,

View File

@ -41,6 +41,7 @@ import (
"gogs.io/gogs/internal/route/admin"
apiv1 "gogs.io/gogs/internal/route/api/v1"
"gogs.io/gogs/internal/route/dev"
"gogs.io/gogs/internal/route/lfs"
"gogs.io/gogs/internal/route/org"
"gogs.io/gogs/internal/route/repo"
"gogs.io/gogs/internal/route/user"
@ -648,11 +649,14 @@ func runWeb(c *cli.Context) error {
// e.g. with or without ".git" suffix.
m.Group("/:reponame([\\d\\w-_\\.]+\\.git$)", func() {
m.Get("", ignSignIn, context.RepoAssignment(), context.RepoRef(), repo.Home)
m.Options("/*", ignSignInAndCsrf, repo.HTTPContexter(), repo.HTTP)
m.Route("/*", "GET,POST", ignSignInAndCsrf, repo.HTTPContexter(), repo.HTTP)
m.Group("/info/lfs", func() {
lfs.RegisterRoutes(m.Router)
}, ignSignInAndCsrf)
m.Route("/*", "GET,POST,OPTIONS", ignSignInAndCsrf, repo.HTTPContexter(), repo.HTTP)
})
m.Options("/:reponame/*", ignSignInAndCsrf, repo.HTTPContexter(), repo.HTTP)
m.Route("/:reponame/*", "GET,POST", ignSignInAndCsrf, repo.HTTPContexter(), repo.HTTP)
m.Route("/:reponame/*", "GET,POST,OPTIONS", ignSignInAndCsrf, repo.HTTPContexter(), repo.HTTP)
})
// ***** END: Repository *****

View File

@ -365,6 +365,8 @@ func Init(customConf string) error {
return errors.Wrap(err, "mapping [cache] section")
} else if err = File.Section("http").MapTo(&HTTP); err != nil {
return errors.Wrap(err, "mapping [http] section")
} else if err = File.Section("lfs").MapTo(&LFS); err != nil {
return errors.Wrap(err, "mapping [lfs] section")
} else if err = File.Section("release").MapTo(&Release); err != nil {
return errors.Wrap(err, "mapping [release] section")
} else if err = File.Section("webhook").MapTo(&Webhook); err != nil {

View File

@ -128,20 +128,7 @@ var (
}
// Database settings
Database struct {
Type string
Host string
Name string
User string
Password string
SSLMode string `ini:"SSL_MODE"`
Path string
// Deprecated: Use Type instead, will be removed in 0.13.
DbType string
// Deprecated: Use Password instead, will be removed in 0.13.
Passwd string
}
Database DatabaseOpts
// Security settings
Security struct {
@ -243,6 +230,11 @@ var (
AccessControlAllowOrigin string
}
// LFS settings
LFS struct {
ObjectsPath string
}
// Attachment settings
Attachment struct {
Enabled bool
@ -252,7 +244,7 @@ var (
MaxFiles int
}
// Release settigns
// Release settings
Release struct {
Attachment struct {
Enabled bool
@ -298,7 +290,7 @@ var (
PagingNum int
}
// Markdown sttings
// Markdown settings
Markdown struct {
EnableHardLineBreak bool
CustomURLSchemes []string `ini:"CUSTOM_URL_SCHEMES"`
@ -409,6 +401,23 @@ var (
HasRobotsTxt bool
)
type DatabaseOpts struct {
Type string
Host string
Name string
User string
Password string
SSLMode string `ini:"SSL_MODE"`
Path string
MaxOpenConns int
MaxIdleConns int
// Deprecated: Use Type instead, will be removed in 0.13.
DbType string
// Deprecated: Use Password instead, will be removed in 0.13.
Passwd string
}
type i18nConf struct {
Langs []string `delim:","`
Names []string `delim:","`

View File

@ -66,6 +66,8 @@ USER=gogs
PASSWORD=12345678
SSL_MODE=disable
PATH=/tmp/gogs.db
MAX_OPEN_CONNS=30
MAX_IDLE_CONNS=30
DB_TYPE=
PASSWD=

View File

@ -133,7 +133,7 @@ func HandleOrgAssignment(c *Context, args ...bool) {
return
}
c.Org.IsTeamAdmin = c.Org.Team.IsOwnerTeam() || c.Org.Team.Authorize >= db.ACCESS_MODE_ADMIN
c.Org.IsTeamAdmin = c.Org.Team.IsOwnerTeam() || c.Org.Team.Authorize >= db.AccessModeAdmin
c.Data["IsTeamAdmin"] = c.Org.IsTeamAdmin
if requireTeamAdmin && !c.Org.IsTeamAdmin {
c.NotFound()

View File

@ -52,22 +52,22 @@ type Repository struct {
// IsOwner returns true if current user is the owner of repository.
func (r *Repository) IsOwner() bool {
return r.AccessMode >= db.ACCESS_MODE_OWNER
return r.AccessMode >= db.AccessModeOwner
}
// IsAdmin returns true if current user has admin or higher access of repository.
func (r *Repository) IsAdmin() bool {
return r.AccessMode >= db.ACCESS_MODE_ADMIN
return r.AccessMode >= db.AccessModeAdmin
}
// IsWriter returns true if current user has write or higher access of repository.
func (r *Repository) IsWriter() bool {
return r.AccessMode >= db.ACCESS_MODE_WRITE
return r.AccessMode >= db.AccessModeWrite
}
// HasAccess returns true if the current user has at least read access for this repository
func (r *Repository) HasAccess() bool {
return r.AccessMode >= db.ACCESS_MODE_READ
return r.AccessMode >= db.AccessModeRead
}
// CanEnableEditor returns true if repository is editable and user has proper access level.
@ -168,7 +168,7 @@ func RepoAssignment(pages ...bool) macaron.Handler {
// Admin has super access.
if c.IsLogged && c.User.IsAdmin {
c.Repo.AccessMode = db.ACCESS_MODE_OWNER
c.Repo.AccessMode = db.AccessModeOwner
} else {
mode, err := db.UserAccessMode(c.UserID(), repo)
if err != nil {
@ -179,7 +179,7 @@ func RepoAssignment(pages ...bool) macaron.Handler {
}
// Check access
if c.Repo.AccessMode == db.ACCESS_MODE_NONE {
if c.Repo.AccessMode == db.AccessModeNone {
// Redirect to any accessible page if not yet on it
if repo.IsPartialPublic() &&
(!(isIssuesPage || isWikiPage) ||

View File

@ -13,22 +13,22 @@ import (
type AccessMode int
const (
ACCESS_MODE_NONE AccessMode = iota // 0
ACCESS_MODE_READ // 1
ACCESS_MODE_WRITE // 2
ACCESS_MODE_ADMIN // 3
ACCESS_MODE_OWNER // 4
AccessModeNone AccessMode = iota // 0
AccessModeRead // 1
AccessModeWrite // 2
AccessModeAdmin // 3
AccessModeOwner // 4
)
func (mode AccessMode) String() string {
switch mode {
case ACCESS_MODE_READ:
case AccessModeRead:
return "read"
case ACCESS_MODE_WRITE:
case AccessModeWrite:
return "write"
case ACCESS_MODE_ADMIN:
case AccessModeAdmin:
return "admin"
case ACCESS_MODE_OWNER:
case AccessModeOwner:
return "owner"
default:
return "none"
@ -39,15 +39,15 @@ func (mode AccessMode) String() string {
func ParseAccessMode(permission string) AccessMode {
switch permission {
case "write":
return ACCESS_MODE_WRITE
return AccessModeWrite
case "admin":
return ACCESS_MODE_ADMIN
return AccessModeAdmin
default:
return ACCESS_MODE_READ
return AccessModeRead
}
}
// Access represents the highest access level of a user to the repository. The only access type
// Access represents the highest access level of a user to a repository. The only access type
// that is not in this table is the real owner of a repository. In case of an organization
// repository, the members of the owners team are in this table.
type Access struct {
@ -58,10 +58,10 @@ type Access struct {
}
func userAccessMode(e Engine, userID int64, repo *Repository) (AccessMode, error) {
mode := ACCESS_MODE_NONE
mode := AccessModeNone
// Everyone has read access to public repository
if !repo.IsPrivate {
mode = ACCESS_MODE_READ
mode = AccessModeRead
}
if userID <= 0 {
@ -69,7 +69,7 @@ func userAccessMode(e Engine, userID int64, repo *Repository) (AccessMode, error
}
if userID == repo.OwnerID {
return ACCESS_MODE_OWNER, nil
return AccessModeOwner, nil
}
access := &Access{
@ -93,6 +93,7 @@ func hasAccess(e Engine, userID int64, repo *Repository, testMode AccessMode) (b
}
// HasAccess returns true if someone has the request access level. User can be nil!
// Deprecated: Use Perms.HasAccess instead.
func HasAccess(userID int64, repo *Repository, testMode AccessMode) (bool, error) {
return hasAccess(x, userID, repo, testMode)
}
@ -136,7 +137,7 @@ func (user *User) GetAccessibleRepositories(limit int) (repos []*Repository, _ e
}
func maxAccessMode(modes ...AccessMode) AccessMode {
max := ACCESS_MODE_NONE
max := AccessModeNone
for _, mode := range modes {
if mode > max {
max = mode
@ -205,7 +206,7 @@ func (repo *Repository) recalculateTeamAccesses(e Engine, ignTeamID int64) (err
// Owner team gets owner access, and skip for teams that do not
// have relations with repository.
if t.IsOwnerTeam() {
t.Authorize = ACCESS_MODE_OWNER
t.Authorize = AccessModeOwner
} else if !t.hasRepository(e, repo.ID) {
continue
}

View File

@ -0,0 +1,65 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package db
import (
"fmt"
"github.com/jinzhu/gorm"
"gogs.io/gogs/internal/errutil"
)
// AccessTokensStore is the persistent interface for access tokens.
//
// NOTE: All methods are sorted in alphabetical order.
type AccessTokensStore interface {
// GetBySHA returns the access token with given SHA1.
// It returns ErrAccessTokenNotExist when not found.
GetBySHA(sha string) (*AccessToken, error)
// Save persists all values of given access token.
Save(t *AccessToken) error
}
var AccessTokens AccessTokensStore
type accessTokens struct {
*gorm.DB
}
var _ errutil.NotFound = (*ErrAccessTokenNotExist)(nil)
type ErrAccessTokenNotExist struct {
args errutil.Args
}
func IsErrAccessTokenNotExist(err error) bool {
_, ok := err.(ErrAccessTokenNotExist)
return ok
}
func (err ErrAccessTokenNotExist) Error() string {
return fmt.Sprintf("access token does not exist: %v", err.args)
}
func (ErrAccessTokenNotExist) NotFound() bool {
return true
}
func (db *accessTokens) GetBySHA(sha string) (*AccessToken, error) {
token := new(AccessToken)
err := db.Where("sha1 = ?", sha).First(token).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, ErrAccessTokenNotExist{args: errutil.Args{"sha": sha}}
}
return nil, err
}
return token, nil
}
func (db *accessTokens) Save(t *AccessToken) error {
return db.DB.Save(t).Error
}

171
internal/db/db.go Normal file
View File

@ -0,0 +1,171 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package db
import (
"fmt"
"io"
"net/url"
"path/filepath"
"strings"
"time"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mssql"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/pkg/errors"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/dbutil"
)
// parsePostgreSQLHostPort parses given input in various forms defined in
// https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
// and returns proper host and port number.
func parsePostgreSQLHostPort(info string) (string, string) {
host, port := "127.0.0.1", "5432"
if strings.Contains(info, ":") && !strings.HasSuffix(info, "]") {
idx := strings.LastIndex(info, ":")
host = info[:idx]
port = info[idx+1:]
} else if len(info) > 0 {
host = info
}
return host, port
}
func parseMSSQLHostPort(info string) (string, string) {
host, port := "127.0.0.1", "1433"
if strings.Contains(info, ":") {
host = strings.Split(info, ":")[0]
port = strings.Split(info, ":")[1]
} else if strings.Contains(info, ",") {
host = strings.Split(info, ",")[0]
port = strings.TrimSpace(strings.Split(info, ",")[1])
} else if len(info) > 0 {
host = info
}
return host, port
}
// parseDSN takes given database options and returns parsed DSN.
func parseDSN(opts conf.DatabaseOpts) (dsn string, err error) {
// In case the database name contains "?" with some parameters
concate := "?"
if strings.Contains(opts.Name, concate) {
concate = "&"
}
switch opts.Type {
case "mysql":
if opts.Host[0] == '/' { // Looks like a unix socket
dsn = fmt.Sprintf("%s:%s@unix(%s)/%s%scharset=utf8mb4&parseTime=true",
opts.User, opts.Password, opts.Host, opts.Name, concate)
} else {
dsn = fmt.Sprintf("%s:%s@tcp(%s)/%s%scharset=utf8mb4&parseTime=true",
opts.User, opts.Password, opts.Host, opts.Name, concate)
}
case "postgres":
host, port := parsePostgreSQLHostPort(opts.Host)
if host[0] == '/' { // looks like a unix socket
dsn = fmt.Sprintf("postgres://%s:%s@:%s/%s%ssslmode=%s&host=%s",
url.QueryEscape(opts.User), url.QueryEscape(opts.Password), port, opts.Name, concate, opts.SSLMode, host)
} else {
dsn = fmt.Sprintf("postgres://%s:%s@%s:%s/%s%ssslmode=%s",
url.QueryEscape(opts.User), url.QueryEscape(opts.Password), host, port, opts.Name, concate, opts.SSLMode)
}
case "mssql":
host, port := parseMSSQLHostPort(opts.Host)
dsn = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;",
host, port, opts.Name, opts.User, opts.Password)
case "sqlite3":
dsn = "file:" + opts.Path + "?cache=shared&mode=rwc"
default:
return "", errors.Errorf("unrecognized dialect: %s", opts.Type)
}
return dsn, nil
}
func openDB(opts conf.DatabaseOpts) (*gorm.DB, error) {
dsn, err := parseDSN(opts)
if err != nil {
return nil, errors.Wrap(err, "parse DSN")
}
return gorm.Open(opts.Type, dsn)
}
func getLogWriter() (io.Writer, error) {
sec := conf.File.Section("log.gorm")
w, err := log.NewFileWriter(
filepath.Join(conf.Log.RootPath, "gorm.log"),
log.FileRotationConfig{
Rotate: sec.Key("ROTATE").MustBool(true),
Daily: sec.Key("ROTATE_DAILY").MustBool(true),
MaxSize: sec.Key("MAX_SIZE").MustInt64(100) * 1024 * 1024,
MaxDays: sec.Key("MAX_DAYS").MustInt64(3),
},
)
if err != nil {
return nil, errors.Wrap(err, `create "gorm.log"`)
}
return w, nil
}
func Init() error {
db, err := openDB(conf.Database)
if err != nil {
return errors.Wrap(err, "open database")
}
db.SingularTable(true)
db.DB().SetMaxOpenConns(conf.Database.MaxOpenConns)
db.DB().SetMaxIdleConns(conf.Database.MaxIdleConns)
db.DB().SetConnMaxLifetime(time.Minute)
w, err := getLogWriter()
if err != nil {
return errors.Wrap(err, "get log writer")
}
db.SetLogger(&dbutil.Writer{Writer: w})
if !conf.IsProdMode() {
db = db.LogMode(true)
}
switch conf.Database.Type {
case "mysql":
conf.UseMySQL = true
db = db.Set("gorm:table_options", "ENGINE=InnoDB")
case "postgres":
conf.UsePostgreSQL = true
case "mssql":
conf.UseMSSQL = true
case "sqlite3":
conf.UseMySQL = true
}
err = db.AutoMigrate(new(LFSObject)).Error
if err != nil {
return errors.Wrap(err, "migrate schemes")
}
// Initialize stores, sorted in alphabetical order.
AccessTokens = &accessTokens{DB: db}
LoginSources = &loginSources{DB: db}
LFS = &lfs{DB: db}
Perms = &perms{DB: db}
Repos = &repos{DB: db}
TwoFactors = &twoFactors{DB: db}
Users = &users{DB: db}
return db.DB().Ping()
}

View File

@ -218,38 +218,6 @@ func (err ErrDeployKeyNameAlreadyUsed) Error() string {
return fmt.Sprintf("public key already exists [repo_id: %d, name: %s]", err.RepoID, err.Name)
}
// _____ ___________ __
// / _ \ ____ ____ ____ ______ _____\__ ___/___ | | __ ____ ____
// / /_\ \_/ ___\/ ___\/ __ \ / ___// ___/ | | / _ \| |/ // __ \ / \
// / | \ \__\ \__\ ___/ \___ \ \___ \ | |( <_> ) <\ ___/| | \
// \____|__ /\___ >___ >___ >____ >____ > |____| \____/|__|_ \\___ >___| /
// \/ \/ \/ \/ \/ \/ \/ \/ \/
type ErrAccessTokenNotExist struct {
SHA string
}
func IsErrAccessTokenNotExist(err error) bool {
_, ok := err.(ErrAccessTokenNotExist)
return ok
}
func (err ErrAccessTokenNotExist) Error() string {
return fmt.Sprintf("access token does not exist [sha: %s]", err.SHA)
}
type ErrAccessTokenEmpty struct {
}
func IsErrAccessTokenEmpty(err error) bool {
_, ok := err.(ErrAccessTokenEmpty)
return ok
}
func (err ErrAccessTokenEmpty) Error() string {
return fmt.Sprintf("access token is empty")
}
// ________ .__ __ .__
// \_____ \_______ _________ ____ |__|____________ _/ |_|__| ____ ____
// / | \_ __ \/ ___\__ \ / \| \___ /\__ \\ __\ |/ _ \ / \

View File

@ -45,16 +45,3 @@ func (err InvalidLoginSourceType) Error() string {
return fmt.Sprintf("invalid login source type [type: %v]", err.Type)
}
type LoginSourceMismatch struct {
Expect int64
Actual int64
}
func IsLoginSourceMismatch(err error) bool {
_, ok := err.(LoginSourceMismatch)
return ok
}
func (err LoginSourceMismatch) Error() string {
return fmt.Sprintf("login source mismatch [expect: %d, actual: %d]", err.Expect, err.Actual)
}

View File

@ -18,16 +18,3 @@ func IsTwoFactorNotFound(err error) bool {
func (err TwoFactorNotFound) Error() string {
return fmt.Sprintf("two-factor authentication does not found [user_id: %d]", err.UserID)
}
type TwoFactorRecoveryCodeNotFound struct {
Code string
}
func IsTwoFactorRecoveryCodeNotFound(err error) bool {
_, ok := err.(TwoFactorRecoveryCodeNotFound)
return ok
}
func (err TwoFactorRecoveryCodeNotFound) Error() string {
return fmt.Sprintf("two-factor recovery code does not found [code: %s]", err.Code)
}

View File

@ -681,7 +681,7 @@ func newIssue(e *xorm.Session, opts NewIssueOptions) (err error) {
// Assume assignee is invalid and drop silently.
opts.Issue.AssigneeID = 0
if assignee != nil {
valid, err := hasAccess(e, assignee.ID, opts.Repo, ACCESS_MODE_READ)
valid, err := hasAccess(e, assignee.ID, opts.Repo, AccessModeRead)
if err != nil {
return fmt.Errorf("hasAccess [user_id: %d, repo_id: %d]: %v", assignee.ID, opts.Repo.ID, err)
}

129
internal/db/lfs.go Normal file
View File

@ -0,0 +1,129 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package db
import (
"fmt"
"io"
"os"
"path/filepath"
"time"
"github.com/jinzhu/gorm"
"github.com/pkg/errors"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/lfsutil"
)
// LFSStore is the persistent interface for LFS objects.
//
// NOTE: All methods are sorted in alphabetical order.
type LFSStore interface {
// CreateObject streams io.ReadCloser to target storage and creates a record in database.
CreateObject(repoID int64, oid lfsutil.OID, rc io.ReadCloser, storage lfsutil.Storage) error
// GetObjectByOID returns the LFS object with given OID. It returns ErrLFSObjectNotExist
// when not found.
GetObjectByOID(repoID int64, oid lfsutil.OID) (*LFSObject, error)
// GetObjectsByOIDs returns LFS objects found within "oids". The returned list could have
// less elements if some oids were not found.
GetObjectsByOIDs(repoID int64, oids ...lfsutil.OID) ([]*LFSObject, error)
}
var LFS LFSStore
type lfs struct {
*gorm.DB
}
// LFSObject is the relation between an LFS object and a repository.
type LFSObject struct {
RepoID int64 `gorm:"PRIMARY_KEY;AUTO_INCREMENT:false"`
OID lfsutil.OID `gorm:"PRIMARY_KEY;column:oid"`
Size int64 `gorm:"NOT NULL"`
Storage lfsutil.Storage `gorm:"NOT NULL"`
CreatedAt time.Time `gorm:"NOT NULL"`
}
func (db *lfs) CreateObject(repoID int64, oid lfsutil.OID, rc io.ReadCloser, storage lfsutil.Storage) (err error) {
if storage != lfsutil.StorageLocal {
return errors.New("only local storage is supported")
}
fpath := lfsutil.StorageLocalPath(conf.LFS.ObjectsPath, oid)
defer func() {
rc.Close()
if err != nil {
_ = os.Remove(fpath)
}
}()
err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm)
if err != nil {
return errors.Wrap(err, "create directories")
}
w, err := os.Create(fpath)
if err != nil {
return errors.Wrap(err, "create file")
}
defer w.Close()
written, err := io.Copy(w, rc)
if err != nil {
return errors.Wrap(err, "copy file")
}
object := &LFSObject{
RepoID: repoID,
OID: oid,
Size: written,
Storage: storage,
}
return db.DB.Create(object).Error
}
type ErrLFSObjectNotExist struct {
args errutil.Args
}
func IsErrLFSObjectNotExist(err error) bool {
_, ok := err.(ErrLFSObjectNotExist)
return ok
}
func (err ErrLFSObjectNotExist) Error() string {
return fmt.Sprintf("LFS object does not exist: %v", err.args)
}
func (ErrLFSObjectNotExist) NotFound() bool {
return true
}
func (db *lfs) GetObjectByOID(repoID int64, oid lfsutil.OID) (*LFSObject, error) {
object := new(LFSObject)
err := db.Where("repo_id = ? AND oid = ?", repoID, oid).First(object).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, ErrLFSObjectNotExist{args: errutil.Args{"repoID": repoID, "oid": oid}}
}
return nil, err
}
return object, err
}
func (db *lfs) GetObjectsByOIDs(repoID int64, oids ...lfsutil.OID) ([]*LFSObject, error) {
if len(oids) == 0 {
return []*LFSObject{}, nil
}
objects := make([]*LFSObject, 0, len(oids))
err := db.Where("repo_id = ? AND oid IN (?)", repoID, oids).Find(&objects).Error
if err != nil && err != gorm.ErrRecordNotFound {
return nil, err
}
return objects, nil
}

View File

@ -35,21 +35,21 @@ type LoginType int
// Note: new type must append to the end of list to maintain compatibility.
const (
LOGIN_NOTYPE LoginType = iota
LOGIN_PLAIN // 1
LOGIN_LDAP // 2
LOGIN_SMTP // 3
LOGIN_PAM // 4
LOGIN_DLDAP // 5
LOGIN_GITHUB // 6
LoginNotype LoginType = iota
LoginPlain // 1
LoginLDAP // 2
LoginSMTP // 3
LoginPAM // 4
LoginDLDAP // 5
LoginGitHub // 6
)
var LoginNames = map[LoginType]string{
LOGIN_LDAP: "LDAP (via BindDN)",
LOGIN_DLDAP: "LDAP (simple auth)", // Via direct bind
LOGIN_SMTP: "SMTP",
LOGIN_PAM: "PAM",
LOGIN_GITHUB: "GitHub",
LoginLDAP: "LDAP (via BindDN)",
LoginDLDAP: "LDAP (simple auth)", // Via direct bind
LoginSMTP: "SMTP",
LoginPAM: "PAM",
LoginGitHub: "GitHub",
}
var SecurityProtocolNames = map[ldap.SecurityProtocol]string{
@ -185,13 +185,13 @@ func (s *LoginSource) BeforeSet(colName string, val xorm.Cell) {
switch colName {
case "type":
switch LoginType(Cell2Int64(val)) {
case LOGIN_LDAP, LOGIN_DLDAP:
case LoginLDAP, LoginDLDAP:
s.Cfg = new(LDAPConfig)
case LOGIN_SMTP:
case LoginSMTP:
s.Cfg = new(SMTPConfig)
case LOGIN_PAM:
case LoginPAM:
s.Cfg = new(PAMConfig)
case LOGIN_GITHUB:
case LoginGitHub:
s.Cfg = new(GitHubConfig)
default:
panic("unrecognized login source type: " + com.ToStr(*val))
@ -213,23 +213,23 @@ func (s *LoginSource) TypeName() string {
}
func (s *LoginSource) IsLDAP() bool {
return s.Type == LOGIN_LDAP
return s.Type == LoginLDAP
}
func (s *LoginSource) IsDLDAP() bool {
return s.Type == LOGIN_DLDAP
return s.Type == LoginDLDAP
}
func (s *LoginSource) IsSMTP() bool {
return s.Type == LOGIN_SMTP
return s.Type == LoginSMTP
}
func (s *LoginSource) IsPAM() bool {
return s.Type == LOGIN_PAM
return s.Type == LoginPAM
}
func (s *LoginSource) IsGitHub() bool {
return s.Type == LOGIN_GITHUB
return s.Type == LoginGitHub
}
func (s *LoginSource) HasTLS() bool {
@ -240,9 +240,9 @@ func (s *LoginSource) HasTLS() bool {
func (s *LoginSource) UseTLS() bool {
switch s.Type {
case LOGIN_LDAP, LOGIN_DLDAP:
case LoginLDAP, LoginDLDAP:
return s.LDAP().SecurityProtocol != ldap.SECURITY_PROTOCOL_UNENCRYPTED
case LOGIN_SMTP:
case LoginSMTP:
return s.SMTP().TLS
}
@ -251,9 +251,9 @@ func (s *LoginSource) UseTLS() bool {
func (s *LoginSource) SkipVerify() bool {
switch s.Type {
case LOGIN_LDAP, LOGIN_DLDAP:
case LoginLDAP, LoginDLDAP:
return s.LDAP().SkipVerify
case LOGIN_SMTP:
case LoginSMTP:
return s.SMTP().SkipVerify
}
@ -293,8 +293,8 @@ func CreateLoginSource(source *LoginSource) error {
return nil
}
// LoginSources returns all login sources defined.
func LoginSources() ([]*LoginSource, error) {
// ListLoginSources returns all login sources defined.
func ListLoginSources() ([]*LoginSource, error) {
sources := make([]*LoginSource, 0, 2)
if err := x.Find(&sources); err != nil {
return nil, err
@ -312,18 +312,6 @@ func ActivatedLoginSources() ([]*LoginSource, error) {
return append(sources, localLoginSources.ActivatedList()...), nil
}
// GetLoginSourceByID returns login source by given ID.
func GetLoginSourceByID(id int64) (*LoginSource, error) {
source := new(LoginSource)
has, err := x.Id(id).Get(source)
if err != nil {
return nil, err
} else if !has {
return localLoginSources.GetLoginSourceByID(id)
}
return source, nil
}
// ResetNonDefaultLoginSources clean other default source flag
func ResetNonDefaultLoginSources(source *LoginSource) error {
// update changes to DB
@ -504,19 +492,19 @@ func LoadAuthSources() {
authType := s.Key("type").String()
switch authType {
case "ldap_bind_dn":
loginSource.Type = LOGIN_LDAP
loginSource.Type = LoginLDAP
loginSource.Cfg = &LDAPConfig{}
case "ldap_simple_auth":
loginSource.Type = LOGIN_DLDAP
loginSource.Type = LoginDLDAP
loginSource.Cfg = &LDAPConfig{}
case "smtp":
loginSource.Type = LOGIN_SMTP
loginSource.Type = LoginSMTP
loginSource.Cfg = &SMTPConfig{}
case "pam":
loginSource.Type = LOGIN_PAM
loginSource.Type = LoginPAM
loginSource.Cfg = &PAMConfig{}
case "github":
loginSource.Type = LOGIN_GITHUB
loginSource.Type = LoginGitHub
loginSource.Cfg = &GitHubConfig{}
default:
log.Fatal("Failed to load authentication source: unknown type '%s'", authType)
@ -552,15 +540,15 @@ func composeFullName(firstname, surname, username string) string {
// LoginViaLDAP queries if login/password is valid against the LDAP directory pool,
// and create a local user if success when enabled.
func LoginViaLDAP(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) {
username, fn, sn, mail, isAdmin, succeed := source.Cfg.(*LDAPConfig).SearchEntry(login, password, source.Type == LOGIN_DLDAP)
func LoginViaLDAP(login, password string, source *LoginSource, autoRegister bool) (*User, error) {
username, fn, sn, mail, isAdmin, succeed := source.Cfg.(*LDAPConfig).SearchEntry(login, password, source.Type == LoginDLDAP)
if !succeed {
// User not in LDAP, do nothing
return nil, ErrUserNotExist{args: map[string]interface{}{"login": login}}
}
if !autoRegister {
return user, nil
return nil, nil
}
// Fallback.
@ -576,7 +564,7 @@ func LoginViaLDAP(user *User, login, password string, source *LoginSource, autoR
mail = fmt.Sprintf("%s@localhost", username)
}
user = &User{
user := &User{
LowerName: strings.ToLower(username),
Name: username,
FullName: composeFullName(fn, sn, username),
@ -669,7 +657,7 @@ func SMTPAuth(a smtp.Auth, cfg *SMTPConfig) error {
// LoginViaSMTP queries if login/password is valid against the SMTP,
// and create a local user if success when enabled.
func LoginViaSMTP(user *User, login, password string, sourceID int64, cfg *SMTPConfig, autoRegister bool) (*User, error) {
func LoginViaSMTP(login, password string, sourceID int64, cfg *SMTPConfig, autoRegister bool) (*User, error) {
// Verify allowed domains.
if len(cfg.AllowedDomains) > 0 {
idx := strings.Index(login, "@")
@ -701,7 +689,7 @@ func LoginViaSMTP(user *User, login, password string, sourceID int64, cfg *SMTPC
}
if !autoRegister {
return user, nil
return nil, nil
}
username := login
@ -710,12 +698,12 @@ func LoginViaSMTP(user *User, login, password string, sourceID int64, cfg *SMTPC
username = login[:idx]
}
user = &User{
user := &User{
LowerName: strings.ToLower(username),
Name: strings.ToLower(username),
Email: login,
Passwd: password,
LoginType: LOGIN_SMTP,
LoginType: LoginSMTP,
LoginSource: sourceID,
LoginName: login,
IsActive: true,
@ -732,7 +720,7 @@ func LoginViaSMTP(user *User, login, password string, sourceID int64, cfg *SMTPC
// LoginViaPAM queries if login/password is valid against the PAM,
// and create a local user if success when enabled.
func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMConfig, autoRegister bool) (*User, error) {
func LoginViaPAM(login, password string, sourceID int64, cfg *PAMConfig, autoRegister bool) (*User, error) {
if err := pam.PAMAuth(cfg.ServiceName, login, password); err != nil {
if strings.Contains(err.Error(), "Authentication failure") {
return nil, ErrUserNotExist{args: map[string]interface{}{"login": login}}
@ -741,15 +729,15 @@ func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMCon
}
if !autoRegister {
return user, nil
return nil, nil
}
user = &User{
user := &User{
LowerName: strings.ToLower(login),
Name: login,
Email: login,
Passwd: password,
LoginType: LOGIN_PAM,
LoginType: LoginPAM,
LoginSource: sourceID,
LoginName: login,
IsActive: true,
@ -757,14 +745,14 @@ func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMCon
return user, CreateUser(user)
}
//________.__ __ ___ ___ ___.
/// _____/|__|/ |_ / | \ __ _\_ |__
/// \ ___| \ __\/ ~ \ | \ __ \
//\ \_\ \ || | \ Y / | / \_\ \
//\______ /__||__| \___|_ /|____/|___ /
//\/ \/ \/
// ________.__ __ ___ ___ ___.
// / _____/|__|/ |_ / | \ __ _\_ |__
// / \ ___| \ __\/ ~ \ | \ __ \
// \ \_\ \ || | \ Y / | / \_\ \
// \______ /__||__| \___|_ /|____/|___ /
// \/ \/ \/
func LoginViaGitHub(user *User, login, password string, sourceID int64, cfg *GitHubConfig, autoRegister bool) (*User, error) {
func LoginViaGitHub(login, password string, sourceID int64, cfg *GitHubConfig, autoRegister bool) (*User, error) {
fullname, email, url, location, err := github.Authenticate(cfg.APIEndpoint, login, password)
if err != nil {
if strings.Contains(err.Error(), "401") {
@ -774,16 +762,16 @@ func LoginViaGitHub(user *User, login, password string, sourceID int64, cfg *Git
}
if !autoRegister {
return user, nil
return nil, nil
}
user = &User{
user := &User{
LowerName: strings.ToLower(login),
Name: login,
FullName: fullname,
Email: email,
Website: url,
Passwd: password,
LoginType: LOGIN_GITHUB,
LoginType: LoginGitHub,
LoginSource: sourceID,
LoginName: login,
IsActive: true,
@ -792,75 +780,21 @@ func LoginViaGitHub(user *User, login, password string, sourceID int64, cfg *Git
return user, CreateUser(user)
}
func remoteUserLogin(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) {
func authenticateViaLoginSource(source *LoginSource, login, password string, autoRegister bool) (*User, error) {
if !source.IsActived {
return nil, errors.LoginSourceNotActivated{SourceID: source.ID}
}
switch source.Type {
case LOGIN_LDAP, LOGIN_DLDAP:
return LoginViaLDAP(user, login, password, source, autoRegister)
case LOGIN_SMTP:
return LoginViaSMTP(user, login, password, source.ID, source.Cfg.(*SMTPConfig), autoRegister)
case LOGIN_PAM:
return LoginViaPAM(user, login, password, source.ID, source.Cfg.(*PAMConfig), autoRegister)
case LOGIN_GITHUB:
return LoginViaGitHub(user, login, password, source.ID, source.Cfg.(*GitHubConfig), autoRegister)
case LoginLDAP, LoginDLDAP:
return LoginViaLDAP(login, password, source, autoRegister)
case LoginSMTP:
return LoginViaSMTP(login, password, source.ID, source.Cfg.(*SMTPConfig), autoRegister)
case LoginPAM:
return LoginViaPAM(login, password, source.ID, source.Cfg.(*PAMConfig), autoRegister)
case LoginGitHub:
return LoginViaGitHub(login, password, source.ID, source.Cfg.(*GitHubConfig), autoRegister)
}
return nil, errors.InvalidLoginSourceType{Type: source.Type}
}
// UserLogin validates user name and password via given login source ID.
// If the loginSourceID is negative, it will abort login process if user is not found.
func UserLogin(username, password string, loginSourceID int64) (*User, error) {
var user *User
if strings.Contains(username, "@") {
user = &User{Email: strings.ToLower(username)}
} else {
user = &User{LowerName: strings.ToLower(username)}
}
hasUser, err := x.Get(user)
if err != nil {
return nil, fmt.Errorf("get user record: %v", err)
}
if hasUser {
// Note: This check is unnecessary but to reduce user confusion at login page
// and make it more consistent at user's perspective.
if loginSourceID >= 0 && user.LoginSource != loginSourceID {
return nil, errors.LoginSourceMismatch{Expect: loginSourceID, Actual: user.LoginSource}
}
// Validate password hash fetched from database for local accounts
if user.LoginType == LOGIN_NOTYPE ||
user.LoginType == LOGIN_PLAIN {
if user.ValidatePassword(password) {
return user, nil
}
return nil, ErrUserNotExist{args: map[string]interface{}{"userID": user.ID, "name": user.Name}}
}
// Remote login to the login source the user is associated with
source, err := GetLoginSourceByID(user.LoginSource)
if err != nil {
return nil, err
}
return remoteUserLogin(user, user.LoginName, password, source, false)
}
// Non-local login source is always greater than 0
if loginSourceID <= 0 {
return nil, ErrUserNotExist{args: map[string]interface{}{"name": username}}
}
source, err := GetLoginSourceByID(loginSourceID)
if err != nil {
return nil, err
}
return remoteUserLogin(nil, username, password, source, true)
}

View File

@ -0,0 +1,36 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package db
import (
"github.com/jinzhu/gorm"
)
// LoginSourcesStore is the persistent interface for login sources.
//
// NOTE: All methods are sorted in alphabetical order.
type LoginSourcesStore interface {
// GetByID returns the login source with given ID.
// It returns ErrLoginSourceNotExist when not found.
GetByID(id int64) (*LoginSource, error)
}
var LoginSources LoginSourcesStore
type loginSources struct {
*gorm.DB
}
func (db *loginSources) GetByID(id int64) (*LoginSource, error) {
source := new(LoginSource)
err := db.Where("id = ?", id).First(source).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return localLoginSources.GetLoginSourceByID(id)
}
return nil, err
}
return source, nil
}

View File

@ -7,7 +7,6 @@ package db
import (
"bufio"
"database/sql"
"errors"
"fmt"
"net/url"
"os"
@ -15,10 +14,7 @@ import (
"strings"
"time"
_ "github.com/denisenkom/go-mssqldb"
_ "github.com/go-sql-driver/mysql"
"github.com/json-iterator/go"
_ "github.com/lib/pq"
"github.com/unknwon/com"
log "unknwon.dev/clog/v2"
"xorm.io/core"
@ -48,8 +44,6 @@ var (
x *xorm.Engine
tables []interface{}
HasEngine bool
EnableSQLite3 bool
)
func init() {
@ -70,35 +64,6 @@ func init() {
}
}
// parsePostgreSQLHostPort parses given input in various forms defined in
// https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
// and returns proper host and port number.
func parsePostgreSQLHostPort(info string) (string, string) {
host, port := "127.0.0.1", "5432"
if strings.Contains(info, ":") && !strings.HasSuffix(info, "]") {
idx := strings.LastIndex(info, ":")
host = info[:idx]
port = info[idx+1:]
} else if len(info) > 0 {
host = info
}
return host, port
}
func parseMSSQLHostPort(info string) (string, string) {
host, port := "127.0.0.1", "1433"
if strings.Contains(info, ":") {
host = strings.Split(info, ":")[0]
port = strings.Split(info, ":")[1]
} else if strings.Contains(info, ",") {
host = strings.Split(info, ",")[0]
port = strings.TrimSpace(strings.Split(info, ",")[1])
} else if len(info) > 0 {
host = info
}
return host, port
}
func getEngine() (*xorm.Engine, error) {
Param := "?"
if strings.Contains(conf.Database.Name, Param) {
@ -133,12 +98,9 @@ func getEngine() (*xorm.Engine, error) {
case "mssql":
conf.UseMSSQL = true
host, port := parseMSSQLHostPort(conf.Database.Host)
connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, conf.Database.Name, conf.Database.User, conf.Database.Passwd)
connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, conf.Database.Name, conf.Database.User, conf.Database.Password)
case "sqlite3":
if !EnableSQLite3 {
return nil, errors.New("this binary version does not build support for SQLite3")
}
if err := os.MkdirAll(path.Dir(conf.Database.Path), os.ModePerm); err != nil {
return nil, fmt.Errorf("create directories: %v", err)
}
@ -183,9 +145,8 @@ func SetEngine() (err error) {
return fmt.Errorf("create 'xorm.log': %v", err)
}
// To prevent mystery "MySQL: invalid connection" error,
// see https://gogs.io/gogs/issues/5532.
x.SetMaxIdleConns(0)
x.SetMaxOpenConns(conf.Database.MaxOpenConns)
x.SetMaxIdleConns(conf.Database.MaxIdleConns)
x.SetConnMaxLifetime(time.Second)
if conf.IsProdMode() {
@ -194,7 +155,7 @@ func SetEngine() (err error) {
x.SetLogger(xorm.NewSimpleLogger(logger))
}
x.ShowSQL(true)
return nil
return Init()
}
func NewEngine() (err error) {
@ -331,13 +292,13 @@ func ImportDatabase(dirPath string, verbose bool) (err error) {
tp := LoginType(com.StrTo(com.ToStr(meta["Type"])).MustInt64())
switch tp {
case LOGIN_LDAP, LOGIN_DLDAP:
case LoginLDAP, LoginDLDAP:
bean.Cfg = new(LDAPConfig)
case LOGIN_SMTP:
case LoginSMTP:
bean.Cfg = new(SMTPConfig)
case LOGIN_PAM:
case LoginPAM:
bean.Cfg = new(PAMConfig)
case LOGIN_GITHUB:
case LoginGitHub:
bean.Cfg = new(GitHubConfig)
default:
return fmt.Errorf("unrecognized login source type:: %v", tp)

View File

@ -1,15 +0,0 @@
// +build sqlite
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package db
import (
_ "github.com/mattn/go-sqlite3"
)
func init() {
EnableSQLite3 = true
}

View File

@ -148,7 +148,7 @@ func CreateOrganization(org, owner *User) (err error) {
OrgID: org.ID,
LowerName: strings.ToLower(OWNER_TEAM),
Name: OWNER_TEAM,
Authorize: ACCESS_MODE_OWNER,
Authorize: AccessModeOwner,
NumMembers: 1,
}
if _, err = sess.Insert(t); err != nil {

View File

@ -47,7 +47,7 @@ func (t *Team) IsOwnerTeam() bool {
// HasWriteAccess returns true if team has at least write level access mode.
func (t *Team) HasWriteAccess() bool {
return t.Authorize >= ACCESS_MODE_WRITE
return t.Authorize >= AccessModeWrite
}
// IsTeamMember returns true if given user is a member of team.
@ -174,7 +174,7 @@ func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (e
return fmt.Errorf("get team members: %v", err)
}
for _, member := range t.Members {
has, err := hasAccess(e, member.ID, repo, ACCESS_MODE_READ)
has, err := hasAccess(e, member.ID, repo, AccessModeRead)
if err != nil {
return err
} else if has {

56
internal/db/perms.go Normal file
View File

@ -0,0 +1,56 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package db
import (
"github.com/jinzhu/gorm"
log "unknwon.dev/clog/v2"
)
// PermsStore is the persistent interface for permissions.
//
// NOTE: All methods are sorted in alphabetical order.
type PermsStore interface {
// AccessMode returns the access mode of given user has to the repository.
AccessMode(userID int64, repo *Repository) AccessMode
// Authorize returns true if the user has as good as desired access mode to
// the repository.
Authorize(userID int64, repo *Repository, desired AccessMode) bool
}
var Perms PermsStore
type perms struct {
*gorm.DB
}
func (db *perms) AccessMode(userID int64, repo *Repository) AccessMode {
var mode AccessMode
// Everyone has read access to public repository.
if !repo.IsPrivate {
mode = AccessModeRead
}
// Quick check to avoid a DB query.
if userID <= 0 {
return mode
}
if userID == repo.OwnerID {
return AccessModeOwner
}
access := new(Access)
err := db.Where("user_id = ? AND repo_id = ?", userID, repo.ID).First(access).Error
if err != nil {
log.Error("Failed to get access [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
return mode
}
return access.Mode
}
func (db *perms) Authorize(userID int64, repo *Repository, desired AccessMode) bool {
return desired <= db.AccessMode(userID, repo)
}

View File

@ -492,7 +492,7 @@ func (repo *Repository) getUsersWithAccesMode(e Engine, mode AccessMode) (_ []*U
// getAssignees returns a list of users who can be assigned to issues in this repository.
func (repo *Repository) getAssignees(e Engine) (_ []*User, err error) {
return repo.getUsersWithAccesMode(e, ACCESS_MODE_READ)
return repo.getUsersWithAccesMode(e, AccessModeRead)
}
// GetAssignees returns all users that have read access and can be assigned to issues
@ -508,7 +508,7 @@ func (repo *Repository) GetAssigneeByID(userID int64) (*User, error) {
// GetWriters returns all users that have write access to the repository.
func (repo *Repository) GetWriters() (_ []*User, err error) {
return repo.getUsersWithAccesMode(x, ACCESS_MODE_WRITE)
return repo.getUsersWithAccesMode(x, AccessModeWrite)
}
// GetMilestoneByID returns the milestone belongs to repository by given ID.
@ -551,7 +551,7 @@ func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) strin
}
func (repo *Repository) HasAccess(userID int64) bool {
has, _ := HasAccess(userID, repo, ACCESS_MODE_READ)
has, _ := HasAccess(userID, repo, AccessModeRead)
return has
}
@ -1666,6 +1666,7 @@ func (ErrRepoNotExist) NotFound() bool {
}
// GetRepositoryByName returns the repository by given name under user if exists.
// Deprecated: Use Repos.GetByName instead.
func GetRepositoryByName(ownerID int64, name string) (*Repository, error) {
repo := &Repository{
OwnerID: ownerID,

View File

@ -175,7 +175,7 @@ func UpdateOrgProtectBranch(repo *Repository, protectBranch *ProtectBranch, whit
userIDs := tool.StringsToInt64s(strings.Split(whitelistUserIDs, ","))
validUserIDs = make([]int64, 0, len(userIDs))
for _, userID := range userIDs {
has, err := HasAccess(userID, repo, ACCESS_MODE_WRITE)
has, err := HasAccess(userID, repo, AccessModeWrite)
if err != nil {
return fmt.Errorf("HasAccess [user_id: %d, repo_id: %d]: %v", userID, protectBranch.RepoID, err)
} else if !has {
@ -193,7 +193,7 @@ func UpdateOrgProtectBranch(repo *Repository, protectBranch *ProtectBranch, whit
if protectBranch.WhitelistTeamIDs != whitelistTeamIDs {
hasTeamsChanged = true
teamIDs := tool.StringsToInt64s(strings.Split(whitelistTeamIDs, ","))
teams, err := GetTeamsHaveAccessToRepo(repo.OwnerID, repo.ID, ACCESS_MODE_WRITE)
teams, err := GetTeamsHaveAccessToRepo(repo.OwnerID, repo.ID, AccessModeWrite)
if err != nil {
return fmt.Errorf("GetTeamsHaveAccessToRepo [org_id: %d, repo_id: %d]: %v", repo.OwnerID, repo.ID, err)
}

View File

@ -22,11 +22,11 @@ type Collaboration struct {
func (c *Collaboration) ModeI18nKey() string {
switch c.Mode {
case ACCESS_MODE_READ:
case AccessModeRead:
return "repo.settings.collaboration.read"
case ACCESS_MODE_WRITE:
case AccessModeWrite:
return "repo.settings.collaboration.write"
case ACCESS_MODE_ADMIN:
case AccessModeAdmin:
return "repo.settings.collaboration.admin"
default:
return "repo.settings.collaboration.undefined"
@ -64,7 +64,7 @@ func (repo *Repository) AddCollaborator(u *User) error {
} else if has {
return nil
}
collaboration.Mode = ACCESS_MODE_WRITE
collaboration.Mode = AccessModeWrite
sess := x.NewSession()
defer sess.Close()
@ -96,9 +96,9 @@ func (c *Collaborator) APIFormat() *api.Collaborator {
return &api.Collaborator{
User: c.User.APIFormat(),
Permissions: api.Permission{
Admin: c.Collaboration.Mode >= ACCESS_MODE_ADMIN,
Push: c.Collaboration.Mode >= ACCESS_MODE_WRITE,
Pull: c.Collaboration.Mode >= ACCESS_MODE_READ,
Admin: c.Collaboration.Mode >= AccessModeAdmin,
Push: c.Collaboration.Mode >= AccessModeWrite,
Pull: c.Collaboration.Mode >= AccessModeRead,
},
}
}
@ -131,7 +131,7 @@ func (repo *Repository) GetCollaborators() ([]*Collaborator, error) {
// ChangeCollaborationAccessMode sets new access mode for the collaboration.
func (repo *Repository) ChangeCollaborationAccessMode(userID int64, mode AccessMode) error {
// Discard invalid input
if mode <= ACCESS_MODE_NONE || mode > ACCESS_MODE_OWNER {
if mode <= AccessModeNone || mode > AccessModeOwner {
return nil
}

38
internal/db/repos.go Normal file
View File

@ -0,0 +1,38 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package db
import (
"strings"
"github.com/jinzhu/gorm"
)
// ReposStore is the persistent interface for repositories.
//
// NOTE: All methods are sorted in alphabetical order.
type ReposStore interface {
// GetByName returns the repository with given owner and name.
// It returns ErrRepoNotExist when not found.
GetByName(ownerID int64, name string) (*Repository, error)
}
var Repos ReposStore
type repos struct {
*gorm.DB
}
func (db *repos) GetByName(ownerID int64, name string) (*Repository, error) {
repo := new(Repository)
err := db.Where("owner_id = ? AND lower_name = ?", ownerID, strings.ToLower(name)).First(repo).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, ErrRepoNotExist{args: map[string]interface{}{"ownerID": ownerID, "name": name}}
}
return nil, err
}
return repo, nil
}

View File

@ -426,7 +426,7 @@ func AddPublicKey(ownerID int64, name, content string) (*PublicKey, error) {
OwnerID: ownerID,
Name: name,
Content: content,
Mode: ACCESS_MODE_WRITE,
Mode: AccessModeWrite,
Type: KEY_TYPE_USER,
}
if err = addKey(sess, key); err != nil {
@ -656,7 +656,7 @@ func AddDeployKey(repoID int64, name, content string) (*DeployKey, error) {
pkey := &PublicKey{
Content: content,
Mode: ACCESS_MODE_READ,
Mode: AccessModeRead,
Type: KEY_TYPE_DEPLOY,
}
has, err := x.Get(pkey)
@ -753,7 +753,7 @@ func DeleteDeployKey(doer *User, id int64) error {
if err != nil {
return fmt.Errorf("GetRepositoryByID: %v", err)
}
yes, err := HasAccess(doer.ID, repo, ACCESS_MODE_ADMIN)
yes, err := HasAccess(doer.ID, repo, AccessModeAdmin)
if err != nil {
return fmt.Errorf("HasAccess: %v", err)
} else if !yes {

View File

@ -16,17 +16,17 @@ import (
// AccessToken represents a personal access token.
type AccessToken struct {
ID int64
UID int64 `xorm:"INDEX"`
Name string
Sha1 string `xorm:"UNIQUE VARCHAR(40)"`
ID int64
UserID int64 `xorm:"uid INDEX" gorm:"COLUMN:uid"`
Name string
Sha1 string `xorm:"UNIQUE VARCHAR(40)"`
Created time.Time `xorm:"-" json:"-"`
Created time.Time `xorm:"-" gorm:"-" json:"-"`
CreatedUnix int64
Updated time.Time `xorm:"-" json:"-"` // Note: Updated must below Created for AfterSet.
Updated time.Time `xorm:"-" gorm:"-" json:"-"` // Note: Updated must below Created for AfterSet.
UpdatedUnix int64
HasRecentActivity bool `xorm:"-" json:"-"`
HasUsed bool `xorm:"-" json:"-"`
HasRecentActivity bool `xorm:"-" gorm:"-" json:"-"`
HasUsed bool `xorm:"-" gorm:"-" json:"-"`
}
func (t *AccessToken) BeforeInsert() {
@ -52,8 +52,8 @@ func (t *AccessToken) AfterSet(colName string, _ xorm.Cell) {
func NewAccessToken(t *AccessToken) error {
t.Sha1 = tool.SHA1(gouuid.NewV4().String())
has, err := x.Get(&AccessToken{
UID: t.UID,
Name: t.Name,
UserID: t.UserID,
Name: t.Name,
})
if err != nil {
return err
@ -65,38 +65,17 @@ func NewAccessToken(t *AccessToken) error {
return err
}
// GetAccessTokenBySHA returns access token by given sha1.
func GetAccessTokenBySHA(sha string) (*AccessToken, error) {
if sha == "" {
return nil, ErrAccessTokenEmpty{}
}
t := &AccessToken{Sha1: sha}
has, err := x.Get(t)
if err != nil {
return nil, err
} else if !has {
return nil, ErrAccessTokenNotExist{sha}
}
return t, nil
}
// ListAccessTokens returns a list of access tokens belongs to given user.
func ListAccessTokens(uid int64) ([]*AccessToken, error) {
tokens := make([]*AccessToken, 0, 5)
return tokens, x.Where("uid=?", uid).Desc("id").Find(&tokens)
}
// UpdateAccessToken updates information of access token.
func UpdateAccessToken(t *AccessToken) error {
_, err := x.Id(t.ID).AllCols().Update(t)
return err
}
// DeleteAccessTokenOfUserByID deletes access token by given ID.
func DeleteAccessTokenOfUserByID(userID, id int64) error {
_, err := x.Delete(&AccessToken{
ID: id,
UID: userID,
ID: id,
UserID: userID,
})
return err
}

View File

@ -12,7 +12,6 @@ import (
"github.com/pquerna/otp/totp"
"github.com/unknwon/com"
log "unknwon.dev/clog/v2"
"xorm.io/xorm"
"gogs.io/gogs/internal/conf"
@ -54,15 +53,6 @@ func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) {
return totp.Validate(passcode, string(decryptSecret)), nil
}
// IsUserEnabledTwoFactor returns true if user has enabled two-factor authentication.
func IsUserEnabledTwoFactor(userID int64) bool {
has, err := x.Where("user_id = ?", userID).Get(new(TwoFactor))
if err != nil {
log.Error("IsUserEnabledTwoFactor [user_id: %d]: %v", userID, err)
}
return has
}
func generateRecoveryCodes(userID int64) ([]*TwoFactorRecoveryCode, error) {
recoveryCodes := make([]*TwoFactorRecoveryCode, 10)
for i := 0; i < 10; i++ {
@ -182,6 +172,19 @@ func RegenerateRecoveryCodes(userID int64) error {
return sess.Commit()
}
type ErrTwoFactorRecoveryCodeNotFound struct {
Code string
}
func IsTwoFactorRecoveryCodeNotFound(err error) bool {
_, ok := err.(ErrTwoFactorRecoveryCodeNotFound)
return ok
}
func (err ErrTwoFactorRecoveryCodeNotFound) Error() string {
return fmt.Sprintf("two-factor recovery code does not found [code: %s]", err.Code)
}
// UseRecoveryCode validates recovery code of given user and marks it is used if valid.
func UseRecoveryCode(userID int64, code string) error {
recoveryCode := new(TwoFactorRecoveryCode)
@ -189,7 +192,7 @@ func UseRecoveryCode(userID int64, code string) error {
if err != nil {
return fmt.Errorf("get unused code: %v", err)
} else if !has {
return errors.TwoFactorRecoveryCodeNotFound{Code: code}
return ErrTwoFactorRecoveryCodeNotFound{Code: code}
}
recoveryCode.IsUsed = true

View File

@ -0,0 +1,33 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package db
import (
"github.com/jinzhu/gorm"
log "unknwon.dev/clog/v2"
)
// TwoFactorsStore is the persistent interface for 2FA.
//
// NOTE: All methods are sorted in alphabetical order.
type TwoFactorsStore interface {
// IsUserEnabled returns true if the user has enabled 2FA.
IsUserEnabled(userID int64) bool
}
var TwoFactors TwoFactorsStore
type twoFactors struct {
*gorm.DB
}
func (db *twoFactors) IsUserEnabled(userID int64) bool {
var count int64
err := db.Model(new(TwoFactor)).Where("user_id = ?", userID).Count(&count).Error
if err != nil {
log.Error("Failed to count two factors [user_id: %d]: %v", userID, err)
}
return count > 0
}

View File

@ -139,9 +139,9 @@ func (u *User) APIFormat() *api.User {
}
}
// returns true if user login type is LOGIN_PLAIN.
// returns true if user login type is LoginPlain.
func (u *User) IsLocal() bool {
return u.LoginType <= LOGIN_PLAIN
return u.LoginType <= LoginPlain
}
// HasForkedRepo checks if user has already forked a repository with given ID.
@ -369,7 +369,7 @@ func (u *User) DeleteAvatar() error {
// IsAdminOfRepo returns true if user has admin or higher access of repository.
func (u *User) IsAdminOfRepo(repo *Repository) bool {
has, err := HasAccess(u.ID, repo, ACCESS_MODE_ADMIN)
has, err := HasAccess(u.ID, repo, AccessModeAdmin)
if err != nil {
log.Error("HasAccess: %v", err)
}
@ -378,7 +378,7 @@ func (u *User) IsAdminOfRepo(repo *Repository) bool {
// IsWriterOfRepo returns true if user has write access to given repository.
func (u *User) IsWriterOfRepo(repo *Repository) bool {
has, err := HasAccess(u.ID, repo, ACCESS_MODE_WRITE)
has, err := HasAccess(u.ID, repo, AccessModeWrite)
if err != nil {
log.Error("HasAccess: %v", err)
}
@ -402,7 +402,7 @@ func (u *User) IsPublicMember(orgId int64) bool {
// IsEnabledTwoFactor returns true if user has enabled two-factor authentication.
func (u *User) IsEnabledTwoFactor() bool {
return IsUserEnabledTwoFactor(u.ID)
return TwoFactors.IsUserEnabled(u.ID)
}
func (u *User) getOrganizationCount(e Engine) (int64, error) {
@ -590,7 +590,7 @@ func CountUsers() int64 {
}
// Users returns number of users in given page.
func Users(page, pageSize int) ([]*User, error) {
func ListUsers(page, pageSize int) ([]*User, error) {
users := make([]*User, 0, pageSize)
return users, x.Limit(pageSize, (page-1)*pageSize).Where("type=0").Asc("id").Find(&users)
}
@ -786,7 +786,7 @@ func deleteUser(e *xorm.Session, u *User) error {
// ***** END: Follow *****
if err = deleteBeans(e,
&AccessToken{UID: u.ID},
&AccessToken{UserID: u.ID},
&Collaboration{UserID: u.ID},
&Access{UserID: u.ID},
&Watch{UserID: u.ID},
@ -922,13 +922,14 @@ func getUserByID(e Engine, id int64) (*User, error) {
}
// GetUserByID returns the user object by given ID if exists.
// Deprecated: Use Users.GetByID instead.
func GetUserByID(id int64) (*User, error) {
return getUserByID(x, id)
}
// GetAssigneeByID returns the user with write access of repository by given ID.
func GetAssigneeByID(repo *Repository, userID int64) (*User, error) {
has, err := HasAccess(userID, repo, ACCESS_MODE_READ)
has, err := HasAccess(userID, repo, AccessModeRead)
if err != nil {
return nil, err
} else if !has {
@ -938,6 +939,7 @@ func GetAssigneeByID(repo *Repository, userID int64) (*User, error) {
}
// GetUserByName returns a user by given name.
// Deprecated: Use Users.GetByUsername instead.
func GetUserByName(name string) (*User, error) {
if len(name) == 0 {
return nil, ErrUserNotExist{args: map[string]interface{}{"name": name}}

138
internal/db/users.go Normal file
View File

@ -0,0 +1,138 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package db
import (
"fmt"
"strings"
"github.com/jinzhu/gorm"
"github.com/pkg/errors"
"gogs.io/gogs/internal/errutil"
)
// UsersStore is the persistent interface for users.
//
// NOTE: All methods are sorted in alphabetical order.
type UsersStore interface {
// Authenticate validates username and password via given login source ID.
// It returns ErrUserNotExist when the user was not found.
//
// When the "loginSourceID" is negative, it aborts the process and returns
// ErrUserNotExist if the user was not found in the database.
//
// When the "loginSourceID" is non-negative, it returns ErrLoginSourceMismatch
// if the user has different login source ID than the "loginSourceID".
//
// When the "loginSourceID" is positive, it tries to authenticate via given
// login source and creates a new user when not yet exists in the database.
Authenticate(username, password string, loginSourceID int64) (*User, error)
// GetByID returns the user with given ID. It returns ErrUserNotExist when not found.
GetByID(id int64) (*User, error)
// GetByUsername returns the user with given username. It returns ErrUserNotExist
// when not found.
GetByUsername(username string) (*User, error)
}
var Users UsersStore
type users struct {
*gorm.DB
}
type ErrLoginSourceMismatch struct {
args errutil.Args
}
func (err ErrLoginSourceMismatch) Error() string {
return fmt.Sprintf("login source mismatch: %v", err.args)
}
func (db *users) Authenticate(username, password string, loginSourceID int64) (*User, error) {
username = strings.ToLower(username)
var query *gorm.DB
if strings.Contains(username, "@") {
query = db.Where("email = ?", username)
} else {
query = db.Where("lower_name = ?", username)
}
user := new(User)
err := query.First(user).Error
if err != nil && err != gorm.ErrRecordNotFound {
return nil, errors.Wrap(err, "get user")
}
// User found in the database
if err == nil {
// Note: This check is unnecessary but to reduce user confusion at login page
// and make it more consistent from user's perspective.
if loginSourceID >= 0 && user.LoginSource != loginSourceID {
return nil, ErrLoginSourceMismatch{args: errutil.Args{"expect": loginSourceID, "actual": user.LoginSource}}
}
// Validate password hash fetched from database for local accounts.
if user.LoginType == LoginNotype || user.LoginType == LoginPlain {
if user.ValidatePassword(password) {
return user, nil
}
return nil, ErrUserNotExist{args: map[string]interface{}{"userID": user.ID, "name": user.Name}}
}
source, err := LoginSources.GetByID(user.LoginSource)
if err != nil {
return nil, errors.Wrap(err, "get login source")
}
_, err = authenticateViaLoginSource(source, username, password, false)
if err != nil {
return nil, errors.Wrap(err, "authenticate via login source")
}
return user, nil
}
// Non-local login source is always greater than 0.
if loginSourceID <= 0 {
return nil, ErrUserNotExist{args: map[string]interface{}{"name": username}}
}
source, err := LoginSources.GetByID(loginSourceID)
if err != nil {
return nil, errors.Wrap(err, "get login source")
}
user, err = authenticateViaLoginSource(source, username, password, true)
if err != nil {
return nil, errors.Wrap(err, "authenticate via login source")
}
return user, nil
}
func (db *users) GetByID(id int64) (*User, error) {
user := new(User)
err := db.Where("id = ?", id).First(user).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, ErrUserNotExist{args: map[string]interface{}{"userID": id}}
}
return nil, err
}
return user, nil
}
func (db *users) GetByUsername(username string) (*User, error) {
user := new(User)
err := db.Where("lower_name = ?", strings.ToLower(username)).First(user).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, ErrUserNotExist{args: map[string]interface{}{"name": username}}
}
return nil, err
}
return user, nil
}

35
internal/dbutil/writer.go Normal file
View File

@ -0,0 +1,35 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package dbutil
import (
"fmt"
"io"
)
// Writer is a wrapper of io.Writer for the gorm.logger.
type Writer struct {
io.Writer
}
func (w *Writer) Print(v ...interface{}) {
if len(v) == 0 {
return
}
if len(v) == 1 {
fmt.Fprint(w.Writer, v[0])
return
}
switch v[0] {
case "sql":
fmt.Fprintf(w.Writer, "[sql] [%s] [%s] %s %v (%d rows affected)", v[1:]...)
case "log":
fmt.Fprintf(w.Writer, "[log] [%s] %s", v[1:]...)
default:
fmt.Fprint(w.Writer, v...)
}
}

View File

@ -0,0 +1,53 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package dbutil
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
)
func TestWriter_Print(t *testing.T) {
tests := []struct {
name string
vs []interface{}
expOutput string
}{
{
name: "no values",
},
{
name: "only one value",
vs: []interface{}{"test"},
expOutput: "test",
},
{
name: "two values",
vs: []interface{}{"test", "output"},
expOutput: "testoutput",
},
{
name: "sql",
vs: []interface{}{"sql", "writer.go:65", "1ms", "SELECT * FROM users WHERE user_id = $1", []int{1}, 1},
expOutput: "[sql] [writer.go:65] [1ms] SELECT * FROM users WHERE user_id = $1 [1] (1 rows affected)",
},
{
name: "log",
vs: []interface{}{"log", "writer.go:65", "something"},
expOutput: "[log] [writer.go:65] something",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var buf bytes.Buffer
w := &Writer{Writer: &buf}
w.Print(test.vs...)
assert.Equal(t, test.expOutput, buf.String())
})
}
}

View File

@ -14,3 +14,6 @@ func IsNotFound(err error) bool {
e, ok := err.(NotFound)
return ok && e.NotFound()
}
// Args is a map of key-value pairs to provide additional context of an error.
type Args map[string]interface{}

View File

@ -10,7 +10,7 @@ import (
// ModuleStore is the interface for Git operations.
//
// NOTE: All methods are sorted in alphabetically.
// NOTE: All methods are sorted in alphabetical order.
type ModuleStore interface {
// AddRemote adds a new remote to the repository in given path.
RepoAddRemote(repoPath, name, url string, opts ...git.AddRemoteOptions) error

30
internal/lfsutil/oid.go Normal file
View File

@ -0,0 +1,30 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package lfsutil
import (
"strings"
)
// OID is an LFS object ID.
type OID string
// ValidOID returns true if given oid is valid according to spec:
// https://github.com/git-lfs/git-lfs/blob/master/docs/spec.md
func ValidOID(oid OID) bool {
fields := strings.SplitN(string(oid), ":", 2)
if len(fields) != 2 {
return false
}
method := fields[0]
hash := fields[1]
switch method {
case "sha256":
// SHA256 produces 64-char lower case hexadecimal hash
return len(hash) == 64 && strings.ToLower(hash) == hash
}
return false
}

View File

@ -0,0 +1,47 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package lfsutil
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestValidOID(t *testing.T) {
tests := []struct {
name string
oid OID
expVal bool
}{
{
name: "malformed",
oid: OID("12345678"),
},
{
name: "unknown method",
oid: OID("sha1:7c222fb2927d828af22f592134e8932480637c0d"),
},
{
name: "sha256: malformed",
oid: OID("sha256:7c222fb2927d828af22f592134e8932480637c0d"),
},
{
name: "sha256: not all lower cased",
oid: OID("sha256:EF797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f"),
},
{
name: "sha256: valid",
oid: OID("sha256:ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f"),
expVal: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expVal, ValidOID(test.oid))
})
}
}

View File

@ -0,0 +1,29 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package lfsutil
import (
"path/filepath"
"strings"
)
// Storage is the storage type of an LFS object.
type Storage string
const (
StorageLocal Storage = "local"
)
// StorageLocalPath returns computed file path for storing object on local file system.
// It returns empty string if given "oid" isn't valid.
func StorageLocalPath(root string, oid OID) string {
if !ValidOID(oid) {
return ""
}
// Valid OID is guaranteed to have second element as hash.
hash := strings.SplitN(string(oid), ":", 2)[1]
return filepath.Join(root, string(hash[0]), string(hash[1]), hash)
}

View File

@ -0,0 +1,43 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package lfsutil
import (
"runtime"
"testing"
"github.com/stretchr/testify/assert"
)
func TestStorageLocalPath(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Skipping testing on Windows")
return
}
tests := []struct {
name string
root string
oid OID
expPath string
}{
{
name: "invalid oid",
oid: OID("bad_oid"),
},
{
name: "valid oid",
root: "/lfs-objects",
oid: OID("sha256:ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f"),
expPath: "/lfs-objects/e/f/ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expPath, StorageLocalPath(test.root, test.oid))
})
}
}

View File

@ -32,7 +32,7 @@ func Authentications(c *context.Context) {
c.PageIs("AdminAuthentications")
var err error
c.Data["Sources"], err = db.LoginSources()
c.Data["Sources"], err = db.ListLoginSources()
if err != nil {
c.Error(err, "list login sources")
return
@ -49,11 +49,11 @@ type dropdownItem struct {
var (
authSources = []dropdownItem{
{db.LoginNames[db.LOGIN_LDAP], db.LOGIN_LDAP},
{db.LoginNames[db.LOGIN_DLDAP], db.LOGIN_DLDAP},
{db.LoginNames[db.LOGIN_SMTP], db.LOGIN_SMTP},
{db.LoginNames[db.LOGIN_PAM], db.LOGIN_PAM},
{db.LoginNames[db.LOGIN_GITHUB], db.LOGIN_GITHUB},
{db.LoginNames[db.LoginLDAP], db.LoginLDAP},
{db.LoginNames[db.LoginDLDAP], db.LoginDLDAP},
{db.LoginNames[db.LoginSMTP], db.LoginSMTP},
{db.LoginNames[db.LoginPAM], db.LoginPAM},
{db.LoginNames[db.LoginGitHub], db.LoginGitHub},
}
securityProtocols = []dropdownItem{
{db.SecurityProtocolNames[ldap.SECURITY_PROTOCOL_UNENCRYPTED], ldap.SECURITY_PROTOCOL_UNENCRYPTED},
@ -67,8 +67,8 @@ func NewAuthSource(c *context.Context) {
c.PageIs("Admin")
c.PageIs("AdminAuthentications")
c.Data["type"] = db.LOGIN_LDAP
c.Data["CurrentTypeName"] = db.LoginNames[db.LOGIN_LDAP]
c.Data["type"] = db.LoginLDAP
c.Data["CurrentTypeName"] = db.LoginNames[db.LoginLDAP]
c.Data["CurrentSecurityProtocol"] = db.SecurityProtocolNames[ldap.SECURITY_PROTOCOL_UNENCRYPTED]
c.Data["smtp_auth"] = "PLAIN"
c.Data["is_active"] = true
@ -131,17 +131,17 @@ func NewAuthSourcePost(c *context.Context, f form.Authentication) {
hasTLS := false
var config core.Conversion
switch db.LoginType(f.Type) {
case db.LOGIN_LDAP, db.LOGIN_DLDAP:
case db.LoginLDAP, db.LoginDLDAP:
config = parseLDAPConfig(f)
hasTLS = ldap.SecurityProtocol(f.SecurityProtocol) > ldap.SECURITY_PROTOCOL_UNENCRYPTED
case db.LOGIN_SMTP:
case db.LoginSMTP:
config = parseSMTPConfig(f)
hasTLS = true
case db.LOGIN_PAM:
case db.LoginPAM:
config = &db.PAMConfig{
ServiceName: f.PAMServiceName,
}
case db.LOGIN_GITHUB:
case db.LoginGitHub:
config = &db.GitHubConfig{
APIEndpoint: strings.TrimSuffix(f.GitHubAPIEndpoint, "/") + "/",
}
@ -186,7 +186,7 @@ func EditAuthSource(c *context.Context) {
c.Data["SecurityProtocols"] = securityProtocols
c.Data["SMTPAuths"] = db.SMTPAuths
source, err := db.GetLoginSourceByID(c.ParamsInt64(":authid"))
source, err := db.LoginSources.GetByID(c.ParamsInt64(":authid"))
if err != nil {
c.Error(err, "get login source by ID")
return
@ -204,7 +204,7 @@ func EditAuthSourcePost(c *context.Context, f form.Authentication) {
c.Data["SMTPAuths"] = db.SMTPAuths
source, err := db.GetLoginSourceByID(c.ParamsInt64(":authid"))
source, err := db.LoginSources.GetByID(c.ParamsInt64(":authid"))
if err != nil {
c.Error(err, "get login source by ID")
return
@ -219,15 +219,15 @@ func EditAuthSourcePost(c *context.Context, f form.Authentication) {
var config core.Conversion
switch db.LoginType(f.Type) {
case db.LOGIN_LDAP, db.LOGIN_DLDAP:
case db.LoginLDAP, db.LoginDLDAP:
config = parseLDAPConfig(f)
case db.LOGIN_SMTP:
case db.LoginSMTP:
config = parseSMTPConfig(f)
case db.LOGIN_PAM:
case db.LoginPAM:
config = &db.PAMConfig{
ServiceName: f.PAMServiceName,
}
case db.LOGIN_GITHUB:
case db.LoginGitHub:
config = &db.GitHubConfig{
APIEndpoint: strings.TrimSuffix(f.GitHubAPIEndpoint, "/") + "/",
}
@ -252,7 +252,7 @@ func EditAuthSourcePost(c *context.Context, f form.Authentication) {
}
func DeleteAuthSource(c *context.Context) {
source, err := db.GetLoginSourceByID(c.ParamsInt64(":authid"))
source, err := db.LoginSources.GetByID(c.ParamsInt64(":authid"))
if err != nil {
c.Error(err, "get login source by ID")
return

View File

@ -32,7 +32,7 @@ func Users(c *context.Context) {
route.RenderUserSearch(c, &route.UserSearchOptions{
Type: db.USER_TYPE_INDIVIDUAL,
Counter: db.CountUsers,
Ranger: db.Users,
Ranger: db.ListUsers,
PageSize: conf.UI.Admin.UserPagingNum,
OrderBy: "id ASC",
TplName: USERS,
@ -46,7 +46,7 @@ func NewUser(c *context.Context) {
c.Data["login_type"] = "0-0"
sources, err := db.LoginSources()
sources, err := db.ListLoginSources()
if err != nil {
c.Error(err, "list login sources")
return
@ -62,7 +62,7 @@ func NewUserPost(c *context.Context, f form.AdminCrateUser) {
c.Data["PageIsAdmin"] = true
c.Data["PageIsAdminUsers"] = true
sources, err := db.LoginSources()
sources, err := db.ListLoginSources()
if err != nil {
c.Error(err, "list login sources")
return
@ -81,7 +81,7 @@ func NewUserPost(c *context.Context, f form.AdminCrateUser) {
Email: f.Email,
Passwd: f.Password,
IsActive: true,
LoginType: db.LOGIN_PLAIN,
LoginType: db.LoginPlain,
}
if len(f.LoginType) > 0 {
@ -132,7 +132,7 @@ func prepareUserInfo(c *context.Context) *db.User {
c.Data["User"] = u
if u.LoginSource > 0 {
c.Data["LoginSource"], err = db.GetLoginSourceByID(u.LoginSource)
c.Data["LoginSource"], err = db.LoginSources.GetByID(u.LoginSource)
if err != nil {
c.Error(err, "get login source by ID")
return nil
@ -141,7 +141,7 @@ func prepareUserInfo(c *context.Context) *db.User {
c.Data["LoginSource"] = &db.LoginSource{}
}
sources, err := db.LoginSources()
sources, err := db.ListLoginSources()
if err != nil {
c.Error(err, "list login sources")
return nil

View File

@ -23,7 +23,7 @@ func parseLoginSource(c *context.APIContext, u *db.User, sourceID int64, loginNa
return
}
source, err := db.GetLoginSourceByID(sourceID)
source, err := db.LoginSources.GetByID(sourceID)
if err != nil {
if errors.IsLoginSourceNotExist(err) {
c.ErrorStatus(http.StatusUnprocessableEntity, err)
@ -45,7 +45,7 @@ func CreateUser(c *context.APIContext, form api.CreateUserOption) {
Email: form.Email,
Passwd: form.Password,
IsActive: true,
LoginType: db.LOGIN_PLAIN,
LoginType: db.LoginPlain,
}
parseLoginSource(c, u, form.SourceID, form.LoginName)

View File

@ -55,7 +55,7 @@ func repoAssignment() macaron.Handler {
}
if c.IsTokenAuth && c.User.IsAdmin {
c.Repo.AccessMode = db.ACCESS_MODE_OWNER
c.Repo.AccessMode = db.AccessModeOwner
} else {
mode, err := db.UserAccessMode(c.UserID(), r)
if err != nil {

View File

@ -131,8 +131,8 @@ func listUserRepositories(c *context.APIContext, username string) {
i := numOwnRepos
for repo, access := range accessibleRepos {
repos[i] = repo.APIFormat(&api.Permission{
Admin: access >= db.ACCESS_MODE_ADMIN,
Push: access >= db.ACCESS_MODE_WRITE,
Admin: access >= db.AccessModeAdmin,
Push: access >= db.AccessModeWrite,
Pull: true,
})
i++

View File

@ -30,8 +30,8 @@ func ListAccessTokens(c *context.APIContext) {
func CreateAccessToken(c *context.APIContext, form api.CreateAccessTokenOption) {
t := &db.AccessToken{
UID: c.User.ID,
Name: form.Name,
UserID: c.User.ID,
Name: form.Name,
}
if err := db.NewAccessToken(t); err != nil {
if errors.IsAccessTokenNameAlreadyExist(err) {

View File

@ -135,7 +135,7 @@ func ExploreUsers(c *context.Context) {
RenderUserSearch(c, &UserSearchOptions{
Type: db.USER_TYPE_INDIVIDUAL,
Counter: db.CountUsers,
Ranger: db.Users,
Ranger: db.ListUsers,
PageSize: conf.UI.ExplorePagingNum,
OrderBy: "updated_unix DESC",
TplName: EXPLORE_USERS,

View File

@ -86,9 +86,6 @@ func GlobalInit(customConf string) error {
db.InitDeliverHooks()
db.InitTestPullRequests()
}
if db.EnableSQLite3 {
log.Info("SQLite3 is supported")
}
if conf.HasMinWinSvc {
log.Info("Builtin Windows Service is supported")
}
@ -125,11 +122,7 @@ func InstallInit(c *context.Context) {
c.Title("install.install")
c.PageIs("Install")
dbOpts := []string{"MySQL", "PostgreSQL", "MSSQL"}
if db.EnableSQLite3 {
dbOpts = append(dbOpts, "SQLite3")
}
c.Data["DbOptions"] = dbOpts
c.Data["DbOptions"] = []string{"MySQL", "PostgreSQL", "MSSQL", "SQLite3"}
}
func Install(c *context.Context) {
@ -148,9 +141,7 @@ func Install(c *context.Context) {
case "mssql":
c.Data["CurDbOption"] = "MSSQL"
case "sqlite3":
if db.EnableSQLite3 {
c.Data["CurDbOption"] = "SQLite3"
}
c.Data["CurDbOption"] = "SQLite3"
}
// Application general settings

122
internal/route/lfs/basic.go Normal file
View File

@ -0,0 +1,122 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package lfs
import (
"encoding/json"
"io"
"net/http"
"os"
"strconv"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/context"
"gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/lfsutil"
"gogs.io/gogs/internal/strutil"
)
const transferBasic = "basic"
const (
basicOperationUpload = "upload"
basicOperationDownload = "download"
)
// GET /{owner}/{repo}.git/info/lfs/object/basic/{oid}
func serveBasicDownload(c *context.Context, repo *db.Repository, oid lfsutil.OID) {
object, err := db.LFS.GetObjectByOID(repo.ID, oid)
if err != nil {
if db.IsErrLFSObjectNotExist(err) {
responseJSON(c.Resp, http.StatusNotFound, responseError{
Message: "Object does not exist",
})
} else {
internalServerError(c.Resp)
log.Error("Failed to get object [repo_id: %d, oid: %s]: %v", repo.ID, oid, err)
}
return
}
fpath := lfsutil.StorageLocalPath(conf.LFS.ObjectsPath, object.OID)
r, err := os.Open(fpath)
if err != nil {
internalServerError(c.Resp)
log.Error("Failed to open object file [path: %s]: %v", fpath, err)
return
}
defer r.Close()
c.Header().Set("Content-Type", "application/octet-stream")
c.Header().Set("Content-Length", strconv.FormatInt(object.Size, 10))
_, err = io.Copy(c.Resp, r)
if err != nil {
c.Status(http.StatusInternalServerError)
log.Error("Failed to copy object file: %v", err)
return
}
c.Status(http.StatusOK)
}
// PUT /{owner}/{repo}.git/info/lfs/object/basic/{oid}
func serveBasicUpload(c *context.Context, repo *db.Repository, oid lfsutil.OID) {
err := db.LFS.CreateObject(repo.ID, oid, c.Req.Request.Body, lfsutil.StorageLocal)
if err != nil {
internalServerError(c.Resp)
log.Error("Failed to create object [repo_id: %d, oid: %s]: %v", repo.ID, oid, err)
return
}
c.Status(http.StatusOK)
log.Trace("[LFS] Object created %q", oid)
}
// POST /{owner}/{repo}.git/info/lfs/object/basic/verify
func serveBasicVerify(c *context.Context, repo *db.Repository) {
var request basicVerifyRequest
defer c.Req.Request.Body.Close()
err := json.NewDecoder(c.Req.Request.Body).Decode(&request)
if err != nil {
responseJSON(c.Resp, http.StatusBadRequest, responseError{
Message: strutil.ToUpperFirst(err.Error()),
})
return
}
if !lfsutil.ValidOID(request.Oid) {
responseJSON(c.Resp, http.StatusBadRequest, responseError{
Message: "Invalid oid",
})
return
}
object, err := db.LFS.GetObjectByOID(repo.ID, lfsutil.OID(request.Oid))
if err != nil {
if db.IsErrLFSObjectNotExist(err) {
responseJSON(c.Resp, http.StatusNotFound, responseError{
Message: "Object does not exist",
})
} else {
internalServerError(c.Resp)
log.Error("Failed to get object [repo_id: %d, oid: %s]: %v", repo.ID, request.Oid, err)
}
return
}
if object.Size != request.Size {
responseJSON(c.Resp, http.StatusNotFound, responseError{
Message: "Object size mismatch",
})
return
}
c.Status(http.StatusOK)
}
type basicVerifyRequest struct {
Oid lfsutil.OID `json:"oid"`
Size int64 `json:"size"`
}

182
internal/route/lfs/batch.go Normal file
View File

@ -0,0 +1,182 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package lfs
import (
"fmt"
"net/http"
jsoniter "github.com/json-iterator/go"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/context"
"gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/lfsutil"
"gogs.io/gogs/internal/strutil"
)
// POST /{owner}/{repo}.git/info/lfs/object/batch
func serveBatch(c *context.Context, owner *db.User, repo *db.Repository) {
var request batchRequest
defer c.Req.Request.Body.Close()
err := jsoniter.NewDecoder(c.Req.Request.Body).Decode(&request)
if err != nil {
responseJSON(c.Resp, http.StatusBadRequest, responseError{
Message: strutil.ToUpperFirst(err.Error()),
})
return
}
// NOTE: We only support basic transfer as of now.
transfer := transferBasic
// Example: https://try.gogs.io/gogs/gogs.git/info/lfs/object/basic
baseHref := fmt.Sprintf("%s%s/%s.git/info/lfs/objects/basic", conf.Server.ExternalURL, owner.Name, repo.Name)
objects := make([]batchObject, 0, len(request.Objects))
switch request.Operation {
case basicOperationUpload:
for _, obj := range request.Objects {
var actions batchActions
if lfsutil.ValidOID(obj.Oid) {
actions = batchActions{
Upload: &batchAction{
Href: fmt.Sprintf("%s/%s", baseHref, obj.Oid),
},
Verify: &batchAction{
Href: fmt.Sprintf("%s/verify", baseHref),
},
}
} else {
actions = batchActions{
Error: &batchError{
Code: http.StatusUnprocessableEntity,
Message: "Object has invalid oid",
},
}
}
objects = append(objects, batchObject{
Oid: obj.Oid,
Size: obj.Size,
Actions: actions,
})
}
case basicOperationDownload:
oids := make([]lfsutil.OID, 0, len(request.Objects))
for _, obj := range request.Objects {
oids = append(oids, obj.Oid)
}
stored, err := db.LFS.GetObjectsByOIDs(repo.ID, oids...)
if err != nil {
internalServerError(c.Resp)
log.Error("Failed to get objects [repo_id: %d, oids: %v]: %v", repo.ID, oids, err)
return
}
storedSet := make(map[lfsutil.OID]*db.LFSObject, len(stored))
for _, obj := range stored {
storedSet[obj.OID] = obj
}
for _, obj := range request.Objects {
var actions batchActions
if stored := storedSet[obj.Oid]; stored != nil {
if stored.Size != obj.Size {
actions.Error = &batchError{
Code: http.StatusUnprocessableEntity,
Message: "Object size mismatch",
}
} else {
actions.Download = &batchAction{
Href: fmt.Sprintf("%s/%s", baseHref, obj.Oid),
}
}
} else {
actions.Error = &batchError{
Code: http.StatusNotFound,
Message: "Object does not exist",
}
}
objects = append(objects, batchObject{
Oid: obj.Oid,
Size: obj.Size,
Actions: actions,
})
}
default:
responseJSON(c.Resp, http.StatusBadRequest, responseError{
Message: "Operation not recognized",
})
return
}
responseJSON(c.Resp, http.StatusOK, batchResponse{
Transfer: transfer,
Objects: objects,
})
}
// batchRequest defines the request payload for the batch endpoint.
type batchRequest struct {
Operation string `json:"operation"`
Objects []struct {
Oid lfsutil.OID `json:"oid"`
Size int64 `json:"size"`
} `json:"objects"`
}
type batchError struct {
Code int `json:"code"`
Message string `json:"message"`
}
type batchAction struct {
Href string `json:"href"`
}
type batchActions struct {
Download *batchAction `json:"download,omitempty"`
Upload *batchAction `json:"upload,omitempty"`
Verify *batchAction `json:"verify,omitempty"`
Error *batchError `json:"error,omitempty"`
}
type batchObject struct {
Oid lfsutil.OID `json:"oid"`
Size int64 `json:"size"`
Actions batchActions `json:"actions"`
}
// batchResponse defines the response payload for the batch endpoint.
type batchResponse struct {
Transfer string `json:"transfer"`
Objects []batchObject `json:"objects"`
}
type responseError struct {
Message string `json:"message"`
}
const contentType = "application/vnd.git-lfs+json"
func responseJSON(w http.ResponseWriter, status int, v interface{}) {
w.Header().Set("Content-Type", contentType)
err := jsoniter.NewEncoder(w).Encode(v)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(status)
}
func internalServerError(w http.ResponseWriter) {
responseJSON(w, http.StatusInternalServerError, responseError{
Message: "Internal server error",
})
}

159
internal/route/lfs/route.go Normal file
View File

@ -0,0 +1,159 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package lfs
import (
"net/http"
"strings"
"time"
"gopkg.in/macaron.v1"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/authutil"
"gogs.io/gogs/internal/context"
"gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/lfsutil"
)
// RegisterRoutes registers LFS routes using given router, and inherits all groups and middleware.
func RegisterRoutes(r *macaron.Router) {
verifyAccept := verifyHeader("Accept", contentType, http.StatusNotAcceptable)
verifyContentTypeJSON := verifyHeader("Content-Type", contentType, http.StatusBadRequest)
verifyContentTypeStream := verifyHeader("Content-Type", "application/octet-stream", http.StatusBadRequest)
r.Group("", func() {
r.Post("/objects/batch", authorize(db.AccessModeRead), verifyAccept, verifyContentTypeJSON, serveBatch)
r.Group("/objects/basic", func() {
r.Combo("/:oid", verifyOID()).
Get(authorize(db.AccessModeRead), serveBasicDownload).
Put(authorize(db.AccessModeWrite), verifyContentTypeStream, serveBasicUpload)
r.Post("/verify", authorize(db.AccessModeWrite), verifyAccept, verifyContentTypeJSON, serveBasicVerify)
})
}, authenticate())
}
// authenticate tries to authenticate user via HTTP Basic Auth.
func authenticate() macaron.Handler {
askCredentials := func(w http.ResponseWriter) {
w.Header().Set("LFS-Authenticate", `Basic realm="Git LFS"`)
responseJSON(w, http.StatusUnauthorized, responseError{
Message: "Credentials needed",
})
}
return func(c *context.Context) {
username, password := authutil.DecodeBasic(c.Req.Header)
if username == "" {
askCredentials(c.Resp)
return
}
user, err := db.Users.Authenticate(username, password, -1)
if err != nil && !db.IsErrUserNotExist(err) {
c.Status(http.StatusInternalServerError)
log.Error("Failed to authenticate user [name: %s]: %v", username, err)
return
}
if err == nil && user.IsEnabledTwoFactor() {
c.PlainText(http.StatusBadRequest, `Users with 2FA enabled are not allowed to authenticate via username and password.`)
return
}
// If username and password authentication failed, try again using username as an access token.
if db.IsErrUserNotExist(err) {
token, err := db.AccessTokens.GetBySHA(username)
if err != nil {
if db.IsErrAccessTokenNotExist(err) {
askCredentials(c.Resp)
} else {
c.Status(http.StatusInternalServerError)
log.Error("Failed to get access token [sha: %s]: %v", username, err)
}
return
}
token.Updated = time.Now()
if err = db.AccessTokens.Save(token); err != nil {
log.Error("Failed to update access token: %v", err)
}
user, err = db.Users.GetByID(token.UserID)
if err != nil {
// Once we found the token, we're supposed to find its related user,
// thus any error is unexpected.
c.Status(http.StatusInternalServerError)
log.Error("Failed to get user: %v", err)
return
}
}
log.Trace("[LFS] Authenticated user: %s", user.Name)
c.Map(user)
}
}
// authorize tries to authorize the user to the context repository with given access mode.
func authorize(mode db.AccessMode) macaron.Handler {
return func(c *context.Context, user *db.User) {
username := c.Params(":username")
reponame := strings.TrimSuffix(c.Params(":reponame"), ".git")
owner, err := db.Users.GetByUsername(username)
if err != nil {
if db.IsErrUserNotExist(err) {
c.Status(http.StatusNotFound)
} else {
c.Status(http.StatusInternalServerError)
log.Error("Failed to get user [name: %s]: %v", username, err)
}
return
}
repo, err := db.Repos.GetByName(owner.ID, reponame)
if err != nil {
if db.IsErrRepoNotExist(err) {
c.Status(http.StatusNotFound)
} else {
c.Status(http.StatusInternalServerError)
log.Error("Failed to get repository [owner_id: %d, name: %s]: %v", owner.ID, reponame, err)
}
return
}
if !db.Perms.Authorize(user.ID, repo, mode) {
c.Status(http.StatusNotFound)
return
}
c.Map(owner)
c.Map(repo)
}
}
// verifyHeader checks if the HTTP header value is matching.
// When not, response given "failCode" as status code.
func verifyHeader(key, value string, failCode int) macaron.Handler {
return func(c *context.Context) {
if c.Req.Header.Get(key) != value {
c.Status(failCode)
return
}
}
}
// verifyOID checks if the ":oid" URL parameter is valid.
func verifyOID() macaron.Handler {
return func(c *context.Context) {
oid := lfsutil.OID(c.Params(":oid"))
if !lfsutil.ValidOID(oid) {
c.PlainText(http.StatusBadRequest, "Invalid oid")
return
}
c.Map(oid)
}
}

View File

@ -110,7 +110,7 @@ func SettingsDelete(c *context.Context) {
org := c.Org.Organization
if c.Req.Method == "POST" {
if _, err := db.UserLogin(c.User.Name, c.Query("password"), c.User.LoginSource); err != nil {
if _, err := db.Users.Authenticate(c.User.Name, c.Query("password"), c.User.LoginSource); err != nil {
if db.IsErrUserNotExist(err) {
c.RenderWithErr(c.Tr("form.enterred_invalid_password"), SETTINGS_DELETE, nil)
} else {

View File

@ -228,11 +228,11 @@ func EditTeamPost(c *context.Context, f form.CreateTeam) {
var auth db.AccessMode
switch f.Permission {
case "read":
auth = db.ACCESS_MODE_READ
auth = db.AccessModeRead
case "write":
auth = db.ACCESS_MODE_WRITE
auth = db.AccessModeWrite
case "admin":
auth = db.ACCESS_MODE_ADMIN
auth = db.AccessModeAdmin
default:
c.Status(http.StatusUnauthorized)
return

View File

@ -12,6 +12,7 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"time"
@ -111,7 +112,7 @@ func HTTPContexter() macaron.Handler {
return
}
authUser, err := db.UserLogin(authUsername, authPassword, -1)
authUser, err := db.Users.Authenticate(authUsername, authPassword, -1)
if err != nil && !db.IsErrUserNotExist(err) {
c.Error(err, "authenticate user")
return
@ -119,9 +120,9 @@ func HTTPContexter() macaron.Handler {
// If username and password combination failed, try again using username as a token.
if authUser == nil {
token, err := db.GetAccessTokenBySHA(authUsername)
token, err := db.AccessTokens.GetBySHA(authUsername)
if err != nil {
if db.IsErrAccessTokenEmpty(err) || db.IsErrAccessTokenNotExist(err) {
if db.IsErrAccessTokenNotExist(err) {
askCredentials(c, http.StatusUnauthorized, "")
} else {
c.Error(err, "get access token by SHA")
@ -129,9 +130,11 @@ func HTTPContexter() macaron.Handler {
return
}
token.Updated = time.Now()
// TODO: verify or update token.Updated in database
if err = db.AccessTokens.Save(token); err != nil {
log.Error("Failed to update access token: %v", err)
}
authUser, err = db.GetUserByID(token.UID)
authUser, err = db.GetUserByID(token.UserID)
if err != nil {
// Once we found token, we're supposed to find its related user,
// thus any error is unexpected.
@ -146,9 +149,9 @@ Please create and use personal access token on user settings page`)
log.Trace("HTTPGit - Authenticated user: %s", authUser.Name)
mode := db.ACCESS_MODE_WRITE
mode := db.AccessModeWrite
if isPull {
mode = db.ACCESS_MODE_READ
mode = db.AccessModeRead
}
has, err := db.HasAccess(authUser.ID, repo, mode)
if err != nil {
@ -367,7 +370,7 @@ func getGitRepoPath(dir string) (string, error) {
dir += ".git"
}
filename := path.Join(conf.Repository.Root, dir)
filename := filepath.Join(conf.Repository.Root, dir)
if _, err := os.Stat(filename); os.IsNotExist(err) {
return "", err
}

View File

@ -513,7 +513,7 @@ func SettingsProtectedBranch(c *context.Context) {
c.Data["Users"] = users
c.Data["whitelist_users"] = protectBranch.WhitelistUserIDs
teams, err := c.Repo.Owner.TeamsHaveAccessToRepo(c.Repo.Repository.ID, db.ACCESS_MODE_WRITE)
teams, err := c.Repo.Owner.TeamsHaveAccessToRepo(c.Repo.Repository.ID, db.AccessModeWrite)
if err != nil {
c.Error(err, "get teams have access to the repository")
return

View File

@ -9,12 +9,12 @@ import (
"net/url"
"github.com/go-macaron/captcha"
"github.com/pkg/errors"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/context"
"gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/db/errors"
"gogs.io/gogs/internal/email"
"gogs.io/gogs/internal/form"
"gogs.io/gogs/internal/tool"
@ -160,13 +160,13 @@ func LoginPost(c *context.Context, f form.SignIn) {
return
}
u, err := db.UserLogin(f.UserName, f.Password, f.LoginSource)
u, err := db.Users.Authenticate(f.UserName, f.Password, f.LoginSource)
if err != nil {
switch err.(type) {
switch errors.Cause(err).(type) {
case db.ErrUserNotExist:
c.FormErr("UserName", "Password")
c.RenderWithErr(c.Tr("form.username_password_incorrect"), LOGIN, &f)
case errors.LoginSourceMismatch:
case db.ErrLoginSourceMismatch:
c.FormErr("LoginSource")
c.RenderWithErr(c.Tr("form.auth_source_mismatch"), LOGIN, &f)
@ -263,7 +263,7 @@ func LoginTwoFactorRecoveryCodePost(c *context.Context) {
}
if err := db.UseRecoveryCode(userID, c.Query("recovery_code")); err != nil {
if errors.IsTwoFactorRecoveryCodeNotFound(err) {
if db.IsTwoFactorRecoveryCodeNotFound(err) {
c.Flash.Error(c.Tr("auth.login_two_factor_invalid_recovery_code"))
c.RedirectSubpath("/user/login/two_factor_recovery_code")
} else {

View File

@ -608,8 +608,8 @@ func SettingsApplicationsPost(c *context.Context, f form.NewAccessToken) {
}
t := &db.AccessToken{
UID: c.User.ID,
Name: f.Name,
UserID: c.User.ID,
Name: f.Name,
}
if err := db.NewAccessToken(t); err != nil {
if errors.IsAccessTokenNameAlreadyExist(err) {
@ -643,7 +643,7 @@ func SettingsDelete(c *context.Context) {
c.PageIs("SettingsDelete")
if c.Req.Method == "POST" {
if _, err := db.UserLogin(c.User.Name, c.Query("password"), c.User.LoginSource); err != nil {
if _, err := db.Users.Authenticate(c.User.Name, c.Query("password"), c.User.LoginSource); err != nil {
if db.IsErrUserNotExist(err) {
c.RenderWithErr(c.Tr("form.enterred_invalid_password"), SETTINGS_DELETE, nil)
} else {

View File

@ -0,0 +1,17 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package strutil
import (
"unicode"
)
// ToUpperFirst returns s with only the first Unicode letter mapped to its upper case.
func ToUpperFirst(s string) string {
for i, v := range s {
return string(unicode.ToUpper(v)) + s[i+1:]
}
return ""
}

View File

@ -0,0 +1,43 @@
// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package strutil
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestToUpperFirst(t *testing.T) {
tests := []struct {
name string
s string
expStr string
}{
{
name: "empty string",
},
{
name: "first letter is a digit",
s: "123 let's go",
expStr: "123 let's go",
},
{
name: "lower to upper",
s: "this is a sentence",
expStr: "This is a sentence",
},
{
name: "already in upper case",
s: "Let's go",
expStr: "Let's go",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expStr, ToUpperFirst(test.s))
})
}
}

View File

@ -29,6 +29,7 @@ func Update(name string) bool {
// the golden file on-demand. It does nothing when the runtime is "windows".
func AssertGolden(t testing.TB, path string, update bool, got interface{}) {
if runtime.GOOS == "windows" {
t.Skip("Skipping testing on Windows")
return
}