From 5a62eb30df3a04e76e465824f525b4ffd920b562 Mon Sep 17 00:00:00 2001 From: Lauris BH Date: Sun, 29 Apr 2018 09:09:24 +0300 Subject: [PATCH] Store OAuth2 session data in database (#3660) * Store OAuth2 session data in database * Rename table to `oauth2_session` and do not skip xormstorage initialization error --- models/oauth2.go | 7 +- modules/auth/oauth2/oauth2.go | 19 +- routers/init.go | 4 +- .../github.com/lafriks/xormstore/Gopkg.lock | 75 ++++++ .../github.com/lafriks/xormstore/Gopkg.toml | 50 ++++ vendor/github.com/lafriks/xormstore/LICENSE | 19 ++ vendor/github.com/lafriks/xormstore/README.md | 48 ++++ vendor/github.com/lafriks/xormstore/test | 70 +++++ .../lafriks/xormstore/util/time_stamp.go | 60 +++++ .../github.com/lafriks/xormstore/xormstore.go | 251 ++++++++++++++++++ vendor/vendor.json | 12 + 11 files changed, 603 insertions(+), 12 deletions(-) create mode 100644 vendor/github.com/lafriks/xormstore/Gopkg.lock create mode 100644 vendor/github.com/lafriks/xormstore/Gopkg.toml create mode 100644 vendor/github.com/lafriks/xormstore/LICENSE create mode 100644 vendor/github.com/lafriks/xormstore/README.md create mode 100755 vendor/github.com/lafriks/xormstore/test create mode 100644 vendor/github.com/lafriks/xormstore/util/time_stamp.go create mode 100644 vendor/github.com/lafriks/xormstore/xormstore.go diff --git a/models/oauth2.go b/models/oauth2.go index a1917540d0..0640471a48 100644 --- a/models/oauth2.go +++ b/models/oauth2.go @@ -97,14 +97,17 @@ func GetActiveOAuth2Providers() ([]string, map[string]OAuth2Provider, error) { } // InitOAuth2 initialize the OAuth2 lib and register all active OAuth2 providers in the library -func InitOAuth2() { - oauth2.Init() +func InitOAuth2() error { + if err := oauth2.Init(x); err != nil { + return err + } loginSources, _ := GetActiveOAuth2ProviderLoginSources() for _, source := range loginSources { oAuth2Config := source.OAuth2() oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret, oAuth2Config.OpenIDConnectAutoDiscoveryURL, oAuth2Config.CustomURLMapping) } + return nil } // wrapOpenIDConnectInitializeError is used to wrap the error but this cannot be done in modules/auth/oauth2 diff --git a/modules/auth/oauth2/oauth2.go b/modules/auth/oauth2/oauth2.go index 4584c48db7..89286a1bd6 100644 --- a/modules/auth/oauth2/oauth2.go +++ b/modules/auth/oauth2/oauth2.go @@ -7,13 +7,12 @@ package oauth2 import ( "math" "net/http" - "os" - "path/filepath" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "github.com/gorilla/sessions" + "github.com/go-xorm/xorm" + "github.com/lafriks/xormstore" "github.com/markbates/goth" "github.com/markbates/goth/gothic" "github.com/markbates/goth/providers/bitbucket" @@ -41,13 +40,14 @@ type CustomURLMapping struct { } // Init initialize the setup of the OAuth2 library -func Init() { - sessionDir := filepath.Join(setting.AppDataPath, "sessions", "oauth2") - if err := os.MkdirAll(sessionDir, 0700); err != nil { - log.Fatal(4, "Fail to create dir %s: %v", sessionDir, err) - } +func Init(x *xorm.Engine) error { + store, err := xormstore.NewOptions(x, xormstore.Options{ + TableName: "oauth2_session", + }, []byte(sessionUsersStoreKey)) - store := sessions.NewFilesystemStore(sessionDir, []byte(sessionUsersStoreKey)) + if err != nil { + return err + } // according to the Goth lib: // set the maxLength of the cookies stored on the disk to a larger number to prevent issues with: // securecookie: the value is too long @@ -65,6 +65,7 @@ func Init() { return req.Header.Get(providerHeaderKey), nil } + return nil } // Auth OAuth2 auth service diff --git a/routers/init.go b/routers/init.go index 3ed5fc4f58..a52f9ca1b4 100644 --- a/routers/init.go +++ b/routers/init.go @@ -60,7 +60,9 @@ func GlobalInit() { log.Fatal(4, "Failed to initialize ORM engine: %v", err) } models.HasEngine = true - models.InitOAuth2() + if err := models.InitOAuth2(); err != nil { + log.Fatal(4, "Failed to initialize OAuth2 support: %v", err) + } models.LoadRepoConfig() models.NewRepoContext() diff --git a/vendor/github.com/lafriks/xormstore/Gopkg.lock b/vendor/github.com/lafriks/xormstore/Gopkg.lock new file mode 100644 index 0000000000..0d2cf03cba --- /dev/null +++ b/vendor/github.com/lafriks/xormstore/Gopkg.lock @@ -0,0 +1,75 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + name = "github.com/denisenkom/go-mssqldb" + packages = ["."] + revision = "ee492709d4324cdcb051d2ac266b77ddc380f5c5" + +[[projects]] + name = "github.com/go-sql-driver/mysql" + packages = ["."] + revision = "a0583e0143b1624142adab07e0e97fe106d99561" + version = "v1.3" + +[[projects]] + branch = "master" + name = "github.com/go-xorm/builder" + packages = ["."] + revision = "488224409dd8aa2ce7a5baf8d10d55764a913738" + +[[projects]] + name = "github.com/go-xorm/core" + packages = ["."] + revision = "da1adaf7a28ca792961721a34e6e04945200c890" + version = "v0.5.7" + +[[projects]] + name = "github.com/go-xorm/xorm" + packages = ["."] + revision = "1933dd69e294c0a26c0266637067f24dbb25770c" + version = "v0.6.4" + +[[projects]] + name = "github.com/gorilla/context" + packages = ["."] + revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a" + version = "v1.1" + +[[projects]] + name = "github.com/gorilla/securecookie" + packages = ["."] + revision = "e59506cc896acb7f7bf732d4fdf5e25f7ccd8983" + version = "v1.1.1" + +[[projects]] + name = "github.com/gorilla/sessions" + packages = ["."] + revision = "ca9ada44574153444b00d3fd9c8559e4cc95f896" + version = "v1.1" + +[[projects]] + branch = "master" + name = "github.com/lib/pq" + packages = [".","oid"] + revision = "88edab0803230a3898347e77b474f8c1820a1f20" + +[[projects]] + name = "github.com/mattn/go-sqlite3" + packages = ["."] + revision = "6c771bb9887719704b210e87e934f08be014bdb1" + version = "v1.6.0" + +[[projects]] + branch = "master" + name = "golang.org/x/crypto" + packages = ["md4"] + revision = "c7dcf104e3a7a1417abc0230cb0d5240d764159d" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "bba98a94e8c6668ae9556b4978bbffdfc5d4d535d522c8865465335bfaa2fc70" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/vendor/github.com/lafriks/xormstore/Gopkg.toml b/vendor/github.com/lafriks/xormstore/Gopkg.toml new file mode 100644 index 0000000000..ea71d50eaf --- /dev/null +++ b/vendor/github.com/lafriks/xormstore/Gopkg.toml @@ -0,0 +1,50 @@ + +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" + + +[[constraint]] + name = "github.com/go-sql-driver/mysql" + version = "1.3.0" + +[[constraint]] + name = "github.com/go-xorm/xorm" + version = "0.6.4" + +[[constraint]] + name = "github.com/gorilla/context" + version = "1.1.0" + +[[constraint]] + name = "github.com/gorilla/securecookie" + version = "1.1.1" + +[[constraint]] + name = "github.com/gorilla/sessions" + version = "1.1.0" + +[[constraint]] + branch = "master" + name = "github.com/lib/pq" + +[[constraint]] + name = "github.com/mattn/go-sqlite3" + version = "1.6.0" diff --git a/vendor/github.com/lafriks/xormstore/LICENSE b/vendor/github.com/lafriks/xormstore/LICENSE new file mode 100644 index 0000000000..7a2dab6c17 --- /dev/null +++ b/vendor/github.com/lafriks/xormstore/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018 Lauris Bukšis-Haberkorns, Mattias Wadman + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/lafriks/xormstore/README.md b/vendor/github.com/lafriks/xormstore/README.md new file mode 100644 index 0000000000..361c5871fc --- /dev/null +++ b/vendor/github.com/lafriks/xormstore/README.md @@ -0,0 +1,48 @@ +[![GoDoc](https://godoc.org/github.com/lafriks/xormstore?status.svg)](https://godoc.org/github.com/lafriks/xormstore) +[![Build Status](https://travis-ci.org/lafriks/xormstore.svg?branch=master)](https://travis-ci.org/lafriks/xormstore) +[![codecov](https://codecov.io/gh/lafriks/xormstore/branch/master/graph/badge.svg)](https://codecov.io/gh/lafriks/xormstore) + +#### XORM backend for gorilla sessions + + go get github.com/lafriks/xormstore + +#### Example + +```go +// initialize and setup cleanup +store := xormstore.New(engine, []byte("secret")) +// db cleanup every hour +// close quit channel to stop cleanup +quit := make(chan struct{}) +go store.PeriodicCleanup(1*time.Hour, quit) +``` + +```go +// in HTTP handler +func handlerFunc(w http.ResponseWriter, r *http.Request) { + session, err := store.Get(r, "session") + session.Values["user_id"] = 123 + store.Save(r, w, session) + http.Error(w, "", http.StatusOK) +} +``` + +For more details see [xormstore godoc documentation](https://godoc.org/github.com/lafriks/xormstore). + +#### Testing + +Just sqlite3 tests: + + go test + +All databases using docker: + + ./test + +If docker is not local (docker-machine etc): + + DOCKER_IP=$(docker-machine ip dev) ./test + +#### License + +xormstore is licensed under the MIT license. See [LICENSE](LICENSE) for the full license text. diff --git a/vendor/github.com/lafriks/xormstore/test b/vendor/github.com/lafriks/xormstore/test new file mode 100755 index 0000000000..4b06eae4e7 --- /dev/null +++ b/vendor/github.com/lafriks/xormstore/test @@ -0,0 +1,70 @@ +#!/bin/bash + +DOCKER_IP=${DOCKER_IP:-127.0.0.1} + +sqlite3() { + DATABASE_URI="sqlite3://file:dummy?mode=memory&cache=shared" go test -v -race -cover -coverprofile=coverage.txt -covermode=atomic + return $? +} + +postgres10() { + ID=$(docker run -p 5432 -d postgres:10-alpine) + PORT=$(docker port "$ID" 5432 | cut -d : -f 2) + DATABASE_URI="postgres://user=postgres password=postgres dbname=postgres host=$DOCKER_IP port=$PORT sslmode=disable" go test -v -race -cover + S=$? + docker rm -vf "$ID" > /dev/null + return $S +} + +postgres96() { + ID=$(docker run -p 5432 -d postgres:9.6-alpine) + PORT=$(docker port "$ID" 5432 | cut -d : -f 2) + DATABASE_URI="postgres://user=postgres password=postgres dbname=postgres host=$DOCKER_IP port=$PORT sslmode=disable" go test -v -race -cover + S=$? + docker rm -vf "$ID" > /dev/null + return $S +} + +postgres94() { + ID=$(docker run -p 5432 -d postgres:9.4-alpine) + PORT=$(docker port "$ID" 5432 | cut -d : -f 2) + DATABASE_URI="postgres://user=postgres password=postgres dbname=postgres host=$DOCKER_IP port=$PORT sslmode=disable" go test -v -race -cover + S=$? + docker rm -vf "$ID" > /dev/null + return $S +} + +mysql57() { + ID=$(docker run \ + -e MYSQL_ROOT_PASSWORD=root \ + -e MYSQL_USER=mysql \ + -e MYSQL_PASSWORD=mysql \ + -e MYSQL_DATABASE=mysql \ + -p 3306 -d mysql:5.7) + PORT=$(docker port "$ID" 3306 | cut -d : -f 2) + DATABASE_URI="mysql://mysql:mysql@tcp($DOCKER_IP:$PORT)/mysql?charset=utf8&parseTime=True" go test -v -race -cover + S=$? + docker rm -vf "$ID" > /dev/null + return $S +} + +mariadb10() { + ID=$(docker run \ + -e MYSQL_ROOT_PASSWORD=root \ + -e MYSQL_USER=mysql \ + -e MYSQL_PASSWORD=mysql \ + -e MYSQL_DATABASE=mysql \ + -p 3306 -d mariadb:10) + PORT=$(docker port "$ID" 3306 | cut -d : -f 2) + DATABASE_URI="mysql://mysql:mysql@tcp($DOCKER_IP:$PORT)/mysql?charset=utf8&parseTime=True" go test -v -race -cover + S=$? + docker rm -vf "$ID" > /dev/null + return $S +} + +sqlite3 || exit 1 +postgres94 || exit 1 +postgres96 || exit 1 +postgres10 || exit 1 +mysql57 || exit 1 +mariadb10 || exit 1 diff --git a/vendor/github.com/lafriks/xormstore/util/time_stamp.go b/vendor/github.com/lafriks/xormstore/util/time_stamp.go new file mode 100644 index 0000000000..2036fdf2f1 --- /dev/null +++ b/vendor/github.com/lafriks/xormstore/util/time_stamp.go @@ -0,0 +1,60 @@ +package util + +import ( + "time" +) + +// TimeStamp defines a timestamp +type TimeStamp int64 + +// TimeStampNow returns now int64 +func TimeStampNow() TimeStamp { + return TimeStamp(time.Now().Unix()) +} + +// Add adds seconds and return sum +func (ts TimeStamp) Add(seconds int64) TimeStamp { + return ts + TimeStamp(seconds) +} + +// AddDuration adds time.Duration and return sum +func (ts TimeStamp) AddDuration(interval time.Duration) TimeStamp { + return ts + TimeStamp(interval/time.Second) +} + +// Year returns the time's year +func (ts TimeStamp) Year() int { + return ts.AsTime().Year() +} + +// AsTime convert timestamp as time.Time in Local locale +func (ts TimeStamp) AsTime() (tm time.Time) { + tm = time.Unix(int64(ts), 0).Local() + return +} + +// AsTimePtr convert timestamp as *time.Time in Local locale +func (ts TimeStamp) AsTimePtr() *time.Time { + tm := time.Unix(int64(ts), 0).Local() + return &tm +} + +// Format formats timestamp as +func (ts TimeStamp) Format(f string) string { + return ts.AsTime().Format(f) +} + +// FormatLong formats as RFC1123Z +func (ts TimeStamp) FormatLong() string { + return ts.Format(time.RFC1123Z) +} + +// FormatShort formats as short +func (ts TimeStamp) FormatShort() string { + return ts.Format("Jan 02, 2006") +} + +// IsZero is zero time +func (ts TimeStamp) IsZero() bool { + return ts.AsTime().IsZero() +} diff --git a/vendor/github.com/lafriks/xormstore/xormstore.go b/vendor/github.com/lafriks/xormstore/xormstore.go new file mode 100644 index 0000000000..f73db2e273 --- /dev/null +++ b/vendor/github.com/lafriks/xormstore/xormstore.go @@ -0,0 +1,251 @@ +/* +Package xormstore is a XORM backend for gorilla sessions + +Simplest form: + + store, err := xormstore.New(engine, []byte("secret-hash-key")) + +All options: + + store, err := xormstore.NewOptions( + engine, // *xorm.Engine + xormstore.Options{ + TableName: "sessions", // "sessions" is default + SkipCreateTable: false, // false is default + }, + []byte("secret-hash-key"), // 32 or 64 bytes recommended, required + []byte("secret-encyption-key")) // nil, 16, 24 or 32 bytes, optional + + if err != nil { + // xormstore can not be initialized + } + + // some more settings, see sessions.Options + store.SessionOpts.Secure = true + store.SessionOpts.HttpOnly = true + store.SessionOpts.MaxAge = 60 * 60 * 24 * 60 + +If you want periodic cleanup of expired sessions: + + quit := make(chan struct{}) + go store.PeriodicCleanup(1*time.Hour, quit) + +For more information about the keys see https://github.com/gorilla/securecookie + +For API to use in HTTP handlers see https://github.com/gorilla/sessions +*/ +package xormstore + +import ( + "encoding/base32" + "net/http" + "strings" + "time" + + "github.com/lafriks/xormstore/util" + + "github.com/go-xorm/xorm" + "github.com/gorilla/context" + "github.com/gorilla/securecookie" + "github.com/gorilla/sessions" +) + +const sessionIDLen = 32 +const defaultTableName = "sessions" +const defaultMaxAge = 60 * 60 * 24 * 30 // 30 days +const defaultPath = "/" + +// Options for xormstore +type Options struct { + TableName string + SkipCreateTable bool +} + +// Store represent a xormstore +type Store struct { + e *xorm.Engine + opts Options + Codecs []securecookie.Codec + SessionOpts *sessions.Options +} + +type xormSession struct { + ID string `xorm:"VARCHAR(400) PK NAME 'id'"` + Data string `xorm:"TEXT"` + CreatedUnix util.TimeStamp `xorm:"created"` + UpdatedUnix util.TimeStamp `xorm:"updated"` + ExpiresUnix util.TimeStamp `xorm:"INDEX"` + + tableName string `xorm:"-"` // just to store table name for easier access +} + +// Define a type for context keys so that they can't clash with anything else stored in context +type contextKey string + +func (xs *xormSession) TableName() string { + return xs.tableName +} + +// New creates a new xormstore session +func New(e *xorm.Engine, keyPairs ...[]byte) (*Store, error) { + return NewOptions(e, Options{}, keyPairs...) +} + +// NewOptions creates a new xormstore session with options +func NewOptions(e *xorm.Engine, opts Options, keyPairs ...[]byte) (*Store, error) { + st := &Store{ + e: e, + opts: opts, + Codecs: securecookie.CodecsFromPairs(keyPairs...), + SessionOpts: &sessions.Options{ + Path: defaultPath, + MaxAge: defaultMaxAge, + }, + } + if st.opts.TableName == "" { + st.opts.TableName = defaultTableName + } + + if !st.opts.SkipCreateTable { + if err := st.e.Sync2(&xormSession{tableName: st.opts.TableName}); err != nil { + return nil, err + } + } + + return st, nil +} + +// Get returns a session for the given name after adding it to the registry. +func (st *Store) Get(r *http.Request, name string) (*sessions.Session, error) { + return sessions.GetRegistry(r).Get(st, name) +} + +// New creates a session with name without adding it to the registry. +func (st *Store) New(r *http.Request, name string) (*sessions.Session, error) { + session := sessions.NewSession(st, name) + opts := *st.SessionOpts + session.Options = &opts + + st.MaxAge(st.SessionOpts.MaxAge) + + // try fetch from db if there is a cookie + if cookie, err := r.Cookie(name); err == nil { + if err := securecookie.DecodeMulti(name, cookie.Value, &session.ID, st.Codecs...); err != nil { + return session, nil + } + s := &xormSession{tableName: st.opts.TableName} + if has, err := st.e.Where("id = ? AND expires_unix >= ?", session.ID, util.TimeStampNow()).Get(s); !has || err != nil { + return session, nil + } + if err := securecookie.DecodeMulti(session.Name(), s.Data, &session.Values, st.Codecs...); err != nil { + return session, nil + } + + context.Set(r, contextKey(name), s) + } + + return session, nil +} + +// Save session and set cookie header +func (st *Store) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error { + s, _ := context.Get(r, contextKey(session.Name())).(*xormSession) + + // delete if max age is < 0 + if session.Options.MaxAge < 0 { + if s != nil { + if _, err := st.e.Delete(&xormSession{ + ID: session.ID, + tableName: st.opts.TableName, + }); err != nil { + return err + } + } + http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options)) + return nil + } + + data, err := securecookie.EncodeMulti(session.Name(), session.Values, st.Codecs...) + if err != nil { + return err + } + now := util.TimeStampNow() + expire := now.AddDuration(time.Second * time.Duration(session.Options.MaxAge)) + + if s == nil { + // generate random session ID key suitable for storage in the db + session.ID = strings.TrimRight( + base32.StdEncoding.EncodeToString( + securecookie.GenerateRandomKey(sessionIDLen)), "=") + s = &xormSession{ + ID: session.ID, + Data: data, + CreatedUnix: now, + UpdatedUnix: now, + ExpiresUnix: expire, + tableName: st.opts.TableName, + } + if _, err := st.e.Insert(s); err != nil { + return err + } + context.Set(r, contextKey(session.Name()), s) + } else { + s.Data = data + s.UpdatedUnix = now + s.ExpiresUnix = expire + if _, err := st.e.ID(s.ID).Cols("data", "updated_unix", "expires_unix").Update(s); err != nil { + return err + } + } + + // set session id cookie + id, err := securecookie.EncodeMulti(session.Name(), session.ID, st.Codecs...) + if err != nil { + return err + } + http.SetCookie(w, sessions.NewCookie(session.Name(), id, session.Options)) + + return nil +} + +// MaxAge sets the maximum age for the store and the underlying cookie +// implementation. Individual sessions can be deleted by setting +// Options.MaxAge = -1 for that session. +func (st *Store) MaxAge(age int) { + st.SessionOpts.MaxAge = age + for _, codec := range st.Codecs { + if sc, ok := codec.(*securecookie.SecureCookie); ok { + sc.MaxAge(age) + } + } +} + +// MaxLength restricts the maximum length of new sessions to l. +// If l is 0 there is no limit to the size of a session, use with caution. +// The default is 4096 (default for securecookie) +func (st *Store) MaxLength(l int) { + for _, c := range st.Codecs { + if codec, ok := c.(*securecookie.SecureCookie); ok { + codec.MaxLength(l) + } + } +} + +// Cleanup deletes expired sessions +func (st *Store) Cleanup() { + st.e.Where("expires_unix < ?", util.TimeStampNow()).Delete(&xormSession{tableName: st.opts.TableName}) +} + +// PeriodicCleanup runs Cleanup every interval. Close quit channel to stop. +func (st *Store) PeriodicCleanup(interval time.Duration, quit <-chan struct{}) { + t := time.NewTicker(interval) + defer t.Stop() + for { + select { + case <-t.C: + st.Cleanup() + case <-quit: + return + } + } +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 631336ac80..937824dc40 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -647,6 +647,18 @@ "revision": "cb6bfca970f6908083f26f39a79009d608efd5cd", "revisionTime": "2016-10-16T15:41:25Z" }, + { + "checksumSHA1": "/X7eCdN7MX8zgCjA9s0ktzgTPlA=", + "path": "github.com/lafriks/xormstore", + "revision": "3a80a383a04b29ec2e1bf61279dd948aa809335b", + "revisionTime": "2018-04-09T10:45:24Z" + }, + { + "checksumSHA1": "Vxvfs8mukr9GOLSuGIPU4ODyOZc=", + "path": "github.com/lafriks/xormstore/util", + "revision": "c0e2f3dc1ecab3536617967e4b47ee5b9e2ca229", + "revisionTime": "2018-03-11T19:16:53Z" + }, { "checksumSHA1": "QV4HZTfaXvhD+5PcGM2p+7aCYYI=", "path": "github.com/lib/pq",