diff --git a/database/boltdb.go b/database/boltdb.go index 505ec19..8671fc8 100644 --- a/database/boltdb.go +++ b/database/boltdb.go @@ -1,12 +1,13 @@ package database import ( - "errors" "time" + "github.com/Xe/ln" "github.com/Xe/uuid" "github.com/asdine/storm" "github.com/brandur/simplebox" + "github.com/pkg/errors" "golang.org/x/net/context" ) @@ -14,9 +15,13 @@ import ( var ( ErrNotImplemented = errors.New("database: not implemented") ErrInvalidKind = errors.New("database: invalid route kind") + ErrRouteAlreadyExists = errors.New("database: route already exists") + ErrTokenAleradyExists = errors.New("database: token already exists") + ErrNoSuchRoute = errors.New("database: no such route") ErrNoSuchToken = errors.New("database: no such token") ErrCantDecryptCert = errors.New("database: can't decrypt cert") ErrUnknownCryptMethod = errors.New("database: unknown encryption method") + ErrUnknown = errors.New("database: unknown error") ) // BoltDBStorage is a backend that uses https://github.com/boltdb/bolt to store @@ -50,11 +55,24 @@ var ( func (b *BoltDBStorage) GetRoute(ctx context.Context, host string) (Route, error) { r := Route{} err := b.db.One("Hostname", host, &r) - return r, err + if err != nil { + ln.Error(err, ln.F{"err": err, "action": "route_get_route"}) + + switch err { + case storm.ErrNotFound: + return errors.Wrapf(err, "%v", ErrNoSuchRoute) + case storm.AlreadyExists: + return errors.Wrapf(err, "%v", ErrRouteAlreadyExists) + default: + return errors.Wrapf(err, "%v", ErrUnknown) + } + } + + return r, nil } -// GetAllRoutes gets all routes out of the database. -func (b *BoltDBStorage) GetAllRoutes(ctx context.Context) ([]Route, error) { +// GetAllRoutes gets all routes out of the database for a given user by username. +func (b *BoltDBStorage) GetAllRoutes(ctx context.Context, user string) ([]Route, error) { rs := []Route{} err := b.db.All(&rs) return rs, err @@ -102,6 +120,22 @@ func (b *BoltDBStorage) GetToken(ctx context.Context, token string) (Token, erro return t, err } +// GetTokenID fetches a token by a given token ID. +func (b *BoltDBStorage) GetTokenID(ctx context.Context, id string) (Token, error) { + t := Token{} + err := b.db.One("ID", id, &t) + if err != nil { + switch err { + case storm.ErrNotFound: + return Token{}, ErrNoSuchToken + default: + return Token{}, err + } + } + + return t, nil +} + // GetTokensForOwner fetches all of the tokens owned by a given owner. func (b *BoltDBStorage) GetTokensForOwner(ctx context.Context, owner string) ([]Token, error) { ts := []Token{} diff --git a/database/certcache.go b/database/certcache.go deleted file mode 100644 index 699d8d4..0000000 --- a/database/certcache.go +++ /dev/null @@ -1,105 +0,0 @@ -package database - -import ( - "errors" - "log" - - r "github.com/GoRethink/gorethink" - "github.com/brandur/simplebox" - "golang.org/x/crypto/acme/autocert" - "golang.org/x/net/context" -) - -// CertCache extends DB to provide certificate management functions for -// https://godoc.org/golang.org/x/crypto/acme/autocert#Cache -type CertCache struct { - *DB - SimpleBox *simplebox.SimpleBox -} - -// CryptoLevel indicates what form of cryptography the certificate is stored -// with. -type CryptoLevel int - -// Crypto levels / strategies defined -const ( - // NOTE: this is defined for debugging / testing usage only - CryptoLevelNone CryptoLevel = iota - - // Use the global set of secretbox keys - CryptoLevelSecretbox -) - -// CachedCert is an individual cached certificate in the database. -type CachedCert struct { - Key string `gorethink:"id" storm:"id"` - CryptoLevel CryptoLevel `gorethink:"cryptoLevel"` - // PEM-encoded bytes with the above crypto level as a filter. - Body []byte `gorethink:"body"` -} - -// Get returns a certificate data for the specified key. -// If there's no such key, Get returns ErrCacheMiss. -func (c *CertCache) Get(ctx context.Context, key string) ([]byte, error) { - res, err := r.Table("certs").Get(key).Run(c.s) - if err != nil { - return nil, err - } - - cert := &CachedCert{} - err = res.One(cert) - if err != nil { - log.Printf("decoding cached cert failed: %v", err) - return nil, autocert.ErrCacheMiss - } - - var body []byte - - switch cert.CryptoLevel { - case CryptoLevelNone: - body = cert.Body - case CryptoLevelSecretbox: - if c.SimpleBox == nil { - return nil, errors.New("can't read this cert, no key in memory") - } - - body, err = c.SimpleBox.Decrypt(cert.Body) - if err != nil { - return nil, autocert.ErrCacheMiss - } - } - - log.Printf("certcache: fetched: %s", key) - - return body, nil -} - -// Put stores the data in the cache under the specified key. -// Underlying implementations may use any data storage format, -// as long as the reverse operation, Get, results in the original data. -func (c *CertCache) Put(ctx context.Context, key string, data []byte) error { - cert := &CachedCert{ - Key: key, - CryptoLevel: CryptoLevelNone, - Body: data, - } - - if c.SimpleBox != nil { - cert.CryptoLevel = CryptoLevelSecretbox - cert.Body = c.SimpleBox.Encrypt(data) - } - - log.Printf("certcache: added: %s", key) - - _, err := r.Table("certs").Insert(cert).RunWrite(c.s) - return err -} - -// Delete removes a certificate data from the cache under the specified key. -// If there's no such key in the cache, Delete returns nil. -func (c *CertCache) Delete(ctx context.Context, key string) error { - _, err := r.Table("certs").Get(key).Delete().Run(c.s) - - log.Printf("certcache: deleted: %s", key) - return err -} diff --git a/database/route.go b/database/route.go index abe72e4..9510b71 100644 --- a/database/route.go +++ b/database/route.go @@ -2,6 +2,7 @@ package database import ( proto "git.xeserv.us/xena/route/proto" + "github.com/Xe/ln" ) // Route is a single HTTP route. @@ -13,6 +14,15 @@ type Route struct { Token string `gorethink:"token" storm:"-"` // deprecated } +// F https://godoc.org/github.com/Xe/ln#F +func (r Route) F() ln.F { + return ln.F{ + "route-id": r.ID, + "route-creator": r.Creator, + "route-hostname": r.Hostname, + } +} + // AsProto converts this into the protobuf. func (r Route) AsProto() *proto.Route { return &proto.Route{ diff --git a/database/storage.go b/database/storage.go index bfd407a..e9783f1 100644 --- a/database/storage.go +++ b/database/storage.go @@ -15,6 +15,7 @@ type Storage interface { // tokens GetToken(ctx context.Context, token string) (Token, error) + GetTokenID(ctx context.Context, id string) (Token, error) GetTokensForOwner(ctx context.Context, owner string) ([]Token, error) PutToken(ctx context.Context, token, owner string, scopes []string) (Token, error) DeleteToken(ctx context.Context, id string) error diff --git a/database/token.go b/database/token.go index f923517..7d50c20 100644 --- a/database/token.go +++ b/database/token.go @@ -1,6 +1,10 @@ package database import "time" +import ( + proto "git.xeserv.us/xena/route/proto" + "github.com/Xe/ln" +) // Token is a single authorization token. type Token struct { @@ -12,3 +16,22 @@ type Token struct { CreatedAt time.Time `json:"created_at"` Active bool `json:"active"` } + +// F https://godoc.org/github.com/Xe/ln#F +func (t Token) F() ln.F { + return ln.F{ + "token-id": t.ID, + "token-owner": t.Owner, + "token-active": t.Active, + } +} + +// AsProto ... +func (t Token) AsProto() *proto.Token { + return &proto.Token{ + Id: t.ID, + Body: t.Body, + Scopes: t.Scopes, + Active: t.Active, + } +} diff --git a/proto/client/.#client.go b/proto/client/.#client.go new file mode 120000 index 0000000..81f4155 --- /dev/null +++ b/proto/client/.#client.go @@ -0,0 +1 @@ +xena@greedo.xeserv.us.17867:1486865539 \ No newline at end of file diff --git a/proto/client/doc.go b/proto/client/doc.go new file mode 100644 index 0000000..b0746d2 --- /dev/null +++ b/proto/client/doc.go @@ -0,0 +1,2 @@ +// Package client is a higer level convenience wrapper around the RPC layer for route. +package client diff --git a/proto/route.pb.go b/proto/route.pb.go index 063302e..71cbc92 100644 --- a/proto/route.pb.go +++ b/proto/route.pb.go @@ -396,7 +396,7 @@ var _Routes_serviceDesc = grpc.ServiceDesc{ type TokensClient interface { Get(ctx context.Context, in *GetTokenRequest, opts ...grpc.CallOption) (*Token, error) GetAll(ctx context.Context, in *Nil, opts ...grpc.CallOption) (*TokenSet, error) - Put(ctx context.Context, in *Token, opts ...grpc.CallOption) (*IDResponse, error) + Put(ctx context.Context, in *Token, opts ...grpc.CallOption) (*Token, error) Delete(ctx context.Context, in *Token, opts ...grpc.CallOption) (*Nil, error) Deactivate(ctx context.Context, in *Token, opts ...grpc.CallOption) (*Nil, error) } @@ -427,8 +427,8 @@ func (c *tokensClient) GetAll(ctx context.Context, in *Nil, opts ...grpc.CallOpt return out, nil } -func (c *tokensClient) Put(ctx context.Context, in *Token, opts ...grpc.CallOption) (*IDResponse, error) { - out := new(IDResponse) +func (c *tokensClient) Put(ctx context.Context, in *Token, opts ...grpc.CallOption) (*Token, error) { + out := new(Token) err := grpc.Invoke(ctx, "/route.Tokens/Put", in, out, c.cc, opts...) if err != nil { return nil, err @@ -459,7 +459,7 @@ func (c *tokensClient) Deactivate(ctx context.Context, in *Token, opts ...grpc.C type TokensServer interface { Get(context.Context, *GetTokenRequest) (*Token, error) GetAll(context.Context, *Nil) (*TokenSet, error) - Put(context.Context, *Token) (*IDResponse, error) + Put(context.Context, *Token) (*Token, error) Delete(context.Context, *Token) (*Nil, error) Deactivate(context.Context, *Token) (*Nil, error) } @@ -590,36 +590,36 @@ var _Tokens_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("route.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 484 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x94, 0xc1, 0x6e, 0xd3, 0x40, - 0x10, 0x86, 0x15, 0x3b, 0x36, 0xed, 0xb4, 0x6a, 0xe9, 0x50, 0x82, 0x31, 0x45, 0x8a, 0x56, 0x20, - 0x45, 0x3d, 0xd4, 0x10, 0x24, 0x10, 0x88, 0x0b, 0x22, 0x21, 0xe2, 0x52, 0x21, 0x97, 0x1b, 0x27, - 0x37, 0x19, 0x15, 0x0b, 0xcb, 0x1b, 0xb2, 0x93, 0x4a, 0x08, 0x71, 0xe1, 0x15, 0x38, 0xf1, 0x3c, - 0x3c, 0x02, 0xaf, 0xc0, 0x83, 0x20, 0xcf, 0x6e, 0x12, 0x27, 0x46, 0xe4, 0xb6, 0xb3, 0x3b, 0xff, - 0xef, 0x7f, 0xbe, 0x89, 0x02, 0x7b, 0x33, 0x3d, 0x67, 0x3a, 0x9b, 0xce, 0x34, 0x6b, 0x0c, 0xa4, - 0x88, 0x4f, 0xae, 0xb4, 0xbe, 0x2a, 0x28, 0xc9, 0xa6, 0x79, 0x92, 0x95, 0xa5, 0xe6, 0x8c, 0x73, - 0x5d, 0x1a, 0xdb, 0xa4, 0x02, 0xf0, 0xcf, 0xf3, 0x42, 0x3d, 0x84, 0xc3, 0x11, 0x71, 0x5a, 0x09, - 0x52, 0xfa, 0x3c, 0x27, 0xc3, 0x88, 0xd0, 0xfe, 0xa8, 0x0d, 0x47, 0xad, 0x6e, 0xab, 0xb7, 0x9b, - 0xca, 0x59, 0x0d, 0x21, 0x90, 0x1e, 0x3c, 0x00, 0x2f, 0x9f, 0xb8, 0x27, 0x2f, 0x9f, 0x60, 0x04, - 0x37, 0xc6, 0x33, 0xca, 0x58, 0xcf, 0x22, 0x4f, 0x2e, 0x17, 0xe5, 0xd2, 0xc6, 0xaf, 0xd9, 0xbc, - 0x84, 0xe3, 0x11, 0xf1, 0xab, 0xa2, 0x10, 0x33, 0x93, 0x92, 0x99, 0xea, 0xd2, 0x10, 0x3e, 0x80, - 0x50, 0x32, 0x9b, 0xa8, 0xd5, 0xf5, 0x7b, 0x7b, 0xfd, 0xfd, 0x33, 0x3b, 0x8f, 0xcd, 0xe5, 0xde, - 0xd4, 0x09, 0xc0, 0xdb, 0xc1, 0x52, 0xb3, 0x91, 0x44, 0x7d, 0x80, 0xe0, 0xbd, 0xfe, 0x44, 0x65, - 0x23, 0x22, 0x42, 0xfb, 0x52, 0x4f, 0xbe, 0xb8, 0x7c, 0x72, 0xc6, 0x0e, 0x84, 0x66, 0xac, 0xa7, - 0x64, 0x22, 0xbf, 0xeb, 0xf7, 0x76, 0x53, 0x57, 0x55, 0xf7, 0xd9, 0x98, 0xf3, 0x6b, 0x8a, 0xda, - 0xdd, 0x56, 0x6f, 0x27, 0x75, 0x95, 0x7a, 0x04, 0x3b, 0x62, 0x7e, 0x41, 0x5c, 0x85, 0xe5, 0xea, - 0xbc, 0x19, 0x56, 0x1a, 0x52, 0xf7, 0xa6, 0x9e, 0x09, 0x58, 0x7b, 0xe7, 0xc0, 0x1e, 0x43, 0x20, - 0x8f, 0x2e, 0x9b, 0x2d, 0x5c, 0x5c, 0x6f, 0x11, 0xb7, 0xff, 0xd3, 0x83, 0xd0, 0xe2, 0xc1, 0x37, - 0xe0, 0x8f, 0x88, 0xb1, 0xe3, 0x3e, 0xb0, 0xb1, 0xa8, 0x78, 0x8d, 0x92, 0xba, 0xfb, 0xfd, 0xf7, - 0x9f, 0x1f, 0xde, 0x2d, 0x3c, 0x4a, 0xae, 0x1f, 0x27, 0x96, 0x58, 0xf2, 0xb5, 0xa2, 0xfe, 0x0d, - 0x87, 0x10, 0x5a, 0xec, 0x08, 0x4e, 0x72, 0x9e, 0x17, 0xf1, 0xbd, 0x95, 0x6d, 0x63, 0x23, 0x0a, - 0xc5, 0x6d, 0x1f, 0x61, 0xe5, 0x86, 0x2f, 0xc0, 0x7f, 0x37, 0x67, 0x5c, 0xfb, 0x6c, 0x7c, 0xe4, - 0xaa, 0xd5, 0x66, 0x16, 0x5a, 0x55, 0xd7, 0x0e, 0x20, 0x1c, 0x50, 0x41, 0x4c, 0xdb, 0xe5, 0x6e, - 0x90, 0xd3, 0xe6, 0x20, 0xfd, 0x5f, 0x1e, 0x84, 0x82, 0xd4, 0xe0, 0xeb, 0x06, 0x9b, 0x3a, 0xeb, - 0x78, 0x6d, 0x29, 0xaa, 0x23, 0x96, 0x37, 0xf1, 0xa0, 0xb2, 0xb4, 0x0b, 0x4a, 0x74, 0x49, 0xf8, - 0xfc, 0x9f, 0x60, 0x0e, 0xeb, 0xda, 0x0b, 0xe2, 0x75, 0x18, 0x56, 0xbe, 0x09, 0x43, 0x7a, 0xb7, - 0xc2, 0x70, 0xda, 0xa7, 0x0d, 0x18, 0x56, 0x5e, 0x0b, 0xb1, 0xd0, 0x9d, 0xd6, 0x75, 0x43, 0x80, - 0x01, 0xc9, 0x2f, 0x32, 0xfb, 0xaf, 0xf6, 0xbe, 0x68, 0xef, 0xa8, 0xdb, 0xb5, 0x71, 0x27, 0x4b, - 0xe1, 0x65, 0x28, 0xff, 0x00, 0x4f, 0xfe, 0x06, 0x00, 0x00, 0xff, 0xff, 0x47, 0x15, 0x47, 0xc2, - 0x35, 0x04, 0x00, 0x00, + // 487 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x93, 0xcf, 0x6e, 0xd3, 0x40, + 0x10, 0xc6, 0x15, 0x3b, 0x36, 0xed, 0xb4, 0x6a, 0xe9, 0x50, 0x82, 0x31, 0x45, 0x8a, 0x56, 0x20, + 0x45, 0x3d, 0xd4, 0x10, 0x04, 0x08, 0xc4, 0x05, 0x91, 0x10, 0x71, 0xa9, 0x90, 0xcb, 0x8d, 0x93, + 0x9b, 0x8c, 0x8a, 0x85, 0xe5, 0x0d, 0xde, 0x4d, 0x24, 0x84, 0xb8, 0xf0, 0x0a, 0x9c, 0x78, 0x19, + 0x5e, 0x82, 0x57, 0xe0, 0x41, 0x90, 0x67, 0xd7, 0x89, 0xff, 0x20, 0xb8, 0xed, 0xec, 0xee, 0xf7, + 0xed, 0x37, 0xbf, 0xb1, 0x61, 0xaf, 0x90, 0x2b, 0x4d, 0x67, 0xcb, 0x42, 0x6a, 0x89, 0x1e, 0x17, + 0xe1, 0xc9, 0x95, 0x94, 0x57, 0x19, 0x45, 0xc9, 0x32, 0x8d, 0x92, 0x3c, 0x97, 0x3a, 0xd1, 0xa9, + 0xcc, 0x95, 0xb9, 0x24, 0x3c, 0x70, 0xcf, 0xd3, 0x4c, 0xdc, 0x87, 0xc3, 0x19, 0xe9, 0xb8, 0x14, + 0xc4, 0xf4, 0x69, 0x45, 0x4a, 0x23, 0x42, 0xff, 0x83, 0x54, 0x3a, 0xe8, 0x0d, 0x7b, 0xa3, 0xdd, + 0x98, 0xd7, 0x62, 0x0a, 0x1e, 0xdf, 0xc1, 0x03, 0x70, 0xd2, 0x85, 0x3d, 0x72, 0xd2, 0x05, 0x06, + 0x70, 0x6d, 0x5e, 0x50, 0xa2, 0x65, 0x11, 0x38, 0xbc, 0x59, 0x95, 0x1b, 0x1b, 0xb7, 0x66, 0xf3, + 0x02, 0x8e, 0x67, 0xa4, 0x5f, 0x66, 0x19, 0x9b, 0xa9, 0x98, 0xd4, 0x52, 0xe6, 0x8a, 0xf0, 0x1e, + 0xf8, 0x9c, 0x59, 0x05, 0xbd, 0xa1, 0x3b, 0xda, 0x1b, 0xef, 0x9f, 0x99, 0x7e, 0x4c, 0x2e, 0x7b, + 0x26, 0x4e, 0x00, 0xde, 0x4c, 0x36, 0x9a, 0x56, 0x12, 0xf1, 0x1e, 0xbc, 0x77, 0xf2, 0x23, 0xe5, + 0x9d, 0x88, 0x08, 0xfd, 0x4b, 0xb9, 0xf8, 0x6c, 0xf3, 0xf1, 0x1a, 0x07, 0xe0, 0xab, 0xb9, 0x5c, + 0x92, 0x0a, 0xdc, 0xa1, 0x3b, 0xda, 0x8d, 0x6d, 0x55, 0xee, 0x27, 0x73, 0x9d, 0xae, 0x29, 0xe8, + 0x0f, 0x7b, 0xa3, 0x9d, 0xd8, 0x56, 0xe2, 0x01, 0xec, 0xb0, 0xf9, 0x05, 0xe9, 0x32, 0xac, 0x2e, + 0xd7, 0xed, 0xb0, 0x7c, 0x21, 0xb6, 0x67, 0xe2, 0x29, 0x83, 0x35, 0x7b, 0x16, 0xec, 0x31, 0x78, + 0x7c, 0x68, 0xb3, 0x99, 0xc2, 0xc6, 0x75, 0xaa, 0xb8, 0xe3, 0x1f, 0x0e, 0xf8, 0x06, 0x0f, 0xbe, + 0x06, 0x77, 0x46, 0x1a, 0x07, 0xf6, 0x81, 0xd6, 0xa0, 0xc2, 0x06, 0x25, 0x71, 0xfb, 0xdb, 0xaf, + 0xdf, 0xdf, 0x9d, 0x1b, 0x78, 0x14, 0xad, 0x1f, 0x46, 0x86, 0x58, 0xf4, 0xa5, 0xa4, 0xfe, 0x15, + 0xa7, 0xe0, 0x1b, 0xec, 0x08, 0x56, 0x72, 0x9e, 0x66, 0xe1, 0x9d, 0xad, 0x6d, 0x67, 0x22, 0x02, + 0xd9, 0x6d, 0x1f, 0x61, 0xeb, 0x86, 0xcf, 0xc1, 0x7d, 0xbb, 0xd2, 0xd8, 0x78, 0x36, 0x3c, 0xb2, + 0xd5, 0x76, 0x32, 0x95, 0x56, 0xd4, 0xb5, 0x13, 0xf0, 0x27, 0x94, 0x91, 0xa6, 0xff, 0xcb, 0x6d, + 0x23, 0xa7, 0xdd, 0x46, 0xc6, 0x3f, 0x1d, 0xf0, 0x19, 0xa9, 0xc2, 0x57, 0x1d, 0x36, 0x75, 0xd6, + 0x61, 0x63, 0x28, 0x62, 0xc0, 0x96, 0xd7, 0xf1, 0xa0, 0xb4, 0x34, 0x03, 0x8a, 0x64, 0x4e, 0xf8, + 0xec, 0xaf, 0x60, 0x0e, 0xeb, 0xda, 0x0b, 0xd2, 0x4d, 0x18, 0x46, 0x8e, 0x8f, 0x9b, 0x30, 0xf8, + 0x6e, 0xeb, 0xd5, 0x06, 0x07, 0x2b, 0x7b, 0xd2, 0xe1, 0x60, 0x94, 0xb5, 0xf7, 0x2b, 0xdd, 0x69, + 0x5d, 0x37, 0x05, 0x98, 0x10, 0x7f, 0x8c, 0xc9, 0x3f, 0xb5, 0x77, 0x59, 0x7b, 0x4b, 0xdc, 0xac, + 0x75, 0xba, 0xd8, 0x08, 0x2f, 0x7d, 0xfe, 0xf9, 0x1f, 0xfd, 0x09, 0x00, 0x00, 0xff, 0xff, 0xd3, + 0x68, 0x6e, 0x4f, 0x30, 0x04, 0x00, 0x00, } diff --git a/proto/route.proto b/proto/route.proto index a385823..5b65d0f 100644 --- a/proto/route.proto +++ b/proto/route.proto @@ -65,7 +65,7 @@ service Tokens { }; } - rpc Put(Token) returns (IDResponse) { + rpc Put(Token) returns (Token) { option (google.api.http) = { post: "/v1/tokens" }; diff --git a/proto/route.swagger.json b/proto/route.swagger.json index b103b14..a86c28b 100644 --- a/proto/route.swagger.json +++ b/proto/route.swagger.json @@ -126,7 +126,7 @@ "200": { "description": "", "schema": { - "$ref": "#/definitions/routeIDResponse" + "$ref": "#/definitions/routeToken" } } }, diff --git a/server/common.go b/server/common.go new file mode 100644 index 0000000..7865e68 --- /dev/null +++ b/server/common.go @@ -0,0 +1,55 @@ +package server + +import ( + "context" + "errors" + + "git.xeserv.us/xena/route/database" + "github.com/Xe/ln" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" +) + +// errors +var ( + ErrNotAuthorized = errors.New("server: not authorized") +) + +func (s *Server) getAuth(ctx context.Context, scope string) (database.Token, error) { + var err error + + md, ok := metadata.FromContext(ctx) + if !ok { + return "", grpc.Errorf(codes.Unauthenticated, "valid token required.") + } + + jwtToken, ok := md["authorization"] + if !ok { + return database.Token{}, grpc.Errorf(codes.Unauthenticated, "valid token required.") + } + val := jwtToken[0] + + t, err := s.db.GetToken(ctx, val) + if err != nil { + return database.Token{}, grpc.Errorf(codes.Unauthenticated, "valid token required.") + } + + ok = false + for _, sc := range t.Scopes { + if sc == scope { + ok = true + } + } + if !ok { + return database.Token{}, grpc.Errorf(codes.Unauthenticated, "invalid scope.") + } + + return t, nil +} + +func handleError(ctx context.Context, clitok database.Token, err error, f ln.F) error { + ln.Error(err, f, clitok.F()) + + return err +} diff --git a/server/route.go b/server/route.go new file mode 100644 index 0000000..e1eeb9c --- /dev/null +++ b/server/route.go @@ -0,0 +1,91 @@ +package server + +import ( + proto "git.xeserv.us/xena/route/proto" + "github.com/Xe/ln" + "github.com/pkg/errors" + "golang.org/x/net/context" +) + +// Route implements rout.RoutesServer for gRPC +type Route struct { + *Server +} + +// interface assertions +var ( + _ proto.RoutesServer = &Route{} +) + +// errors +var () + +// generic error message +const ( + errorMsg = "internal service error, verify your parameters and try again later" +) + +// Get fetches a route from the database. +func (r *Route) Get(ctx context.Context, req *proto.GetRouteRequest) (*proto.Route, error) { + val, err := r.db.GetRoute(ctx, req.Host) + if err != nil { + ln.Error(err, ln.F{"action": "Route.Get"}) + + return nil, err + } + + return val.AsProto(), nil +} + +// GetAll fetches all of the routes that you own. +func (r *Route) GetAll(ctx context.Context, req *proto.Nil) (*proto.GetAllRoutesResponse, error) { + routes, err := r.db.GetAllRoutes() + if err != nil { + ln.Error(err, ln.F{"action": "Route.GetAll"}) + + return nil, err + } + + result := []*proto.Route{} + + // let result = apply routeAsProto routes + for _, rt := range routes { + result = append(result, rt.AsProto()) + } + + return &proto.GetAllRoutesResponse{ + Routes: result, + }, nil +} + +func (r *Route) Put(ctx context.Context, rt *proto.Route) (*proto.IDResponse, error) { + drt, err := r.db.PutRoute(ctx, rt.Host, "http") + if err != nil { + ln.Error(err, ln.F{"action": "Route.Put"}) + + return nil, err + } + + return &proto.IDResponse{ + Id: drt.ID, + } +} + +func (r *Route) Delete(ctx context.Context, rt *proto.Route) (*proto.IDResponse, error) { + drt, err := r.db.GetRoute(ctx, rt.Host) + if err != nil { + ln.Error(err, ln.F{"action": "Route.Delete_getRoute_verify"}) + + return nil, err + } + + err := r.db.DeleteRoute(ctx, rt.Id) + f := drt.F() + f["action"] = "Route.Delete_db.DeleteRoute" + if err != nil { + ln.Error(err, f) + return nil, err + } + + return &proto.IDResponse{Id: rt.Id}, nil +} diff --git a/server/token.go b/server/token.go new file mode 100644 index 0000000..08d2066 --- /dev/null +++ b/server/token.go @@ -0,0 +1,108 @@ +package server + +import ( + "context" + + "github.com/Xe/ln" + "github.com/Xe/uuid" + + proto "git.xeserv.us/xena/route/proto" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// Token is the token server implementation for grpc use. +type Token struct { + *Server +} + +// interface assertions +var ( + _ proto.TokensServer = &Token{} +) + +func (t *Token) Get(ctx context.Context, req *proto.GetTokenRequest) (*proto.Token, error) { + clitok, err := t.getAuth(ctx, "token:get") + if err != nil { + return nil, handleError(ctx, clitok, err, ln.F{"action": "Token.Get_getAuth"}) + } + + if req.Id == "" { + return nil, status.Errorf(codes.InvalidArgument, "must specify ID") + } + + dbt, err := t.db.GetTokenID(ctx, req.Id) + if err != nil { + return nil, err + } + + if dbt.Owner != clitok.Owner { + return nil, ErrNotAuthorized + } + + return dbt.AsProto(), nil +} + +func (t *Token) GetAll(ctx context.Context, req *proto.Nil) (*proto.TokenSet, error) { + clitok, err := t.getAuth(ctx, "token:getall") + if err != nil { + return nil, handleError(ctx, clitok, err, ln.F{"action": "Token.GetAll_getAuth"}) + } + + toks, err := t.db.GetTokensForOwner(ctx, clitok.Owner) + if err != nil { + return nil, handleError(ctx, clitok, err, ln.F{"action": "Token.GetAll_db.GetTokensForOwner"}) + } + + result := []*proto.Token{} + + for _, tok := range toks { + result = append(result, tok.AsProto()) + } + + return &proto.TokenSet{ + Tokens: result, + }, nil +} + +func (t *Token) Put(ctx context.Context, tok *proto.Token) (*proto.Token, error) { + clitok, err := t.getAuth(ctx, "token:put") + if err != nil { + return nil, handleError(ctx, clitok, err, ln.F{"action": "Token.Put_getAuth"}) + } + + dbt, err := t.db.PutToken(ctx, uuid.New(), clitok.Owner, tok.Scopes) + if err != nil { + return nil, handleError(ctx, clitok, err, ln.F{"action": "Token.Put_db.PutToken"}) + } + + return dbt.AsProto(), nil +} + +func (t *Token) Delete(ctx context.Context, tok *proto.Token) (*proto.Nil, error) { + clitok, err := t.getAuth(ctx, "token:delete") + if err != nil { + return nil, handleError(ctx, clitok, err, ln.F{"action": "Token.Delete_getAuth"}) + } + + err = t.db.DeleteToken(ctx, tok.Id) + if err != nil { + return nil, handleError(ctx, clitok, err, ln.F{"action": "Token.Delete_db.DeleteToken"}) + } + + return &proto.Nil{}, nil +} + +func (t *Token) Deactivate(ctx context.Context, tok *proto.Token) (*proto.Nil, error) { + clitok, err := t.getAuth(ctx, "token:deactivate") + if err != nil { + return nil, handleError(ctx, clitok, err, ln.F{"action": "Token.Deactivate_getAuth"}) + } + + err = t.db.DeactivateToken(ctx, tok.Id) + if err != nil { + return nil, handleError(ctx, clitok, err, ln.F{"action": "Token.Deactivate_db.DeactivateToken"}) + } + + return &proto.Nil{}, nil +} diff --git a/vendor-log b/vendor-log index ace8291..50cf9c1 100644 --- a/vendor-log +++ b/vendor-log @@ -118,3 +118,8 @@ f6c17b524822278a87e3b3bd809fec33b51f5b46 github.com/emirpasic/gods/containers f6c17b524822278a87e3b3bd809fec33b51f5b46 github.com/emirpasic/gods/trees f6c17b524822278a87e3b3bd809fec33b51f5b46 github.com/emirpasic/gods/trees/redblacktree f6c17b524822278a87e3b3bd809fec33b51f5b46 github.com/emirpasic/gods/utils +18c9bb3261723cd5401db4d0c9fbc5c3b6c70fe8 github.com/golang/protobuf/proto +18c9bb3261723cd5401db4d0c9fbc5c3b6c70fe8 github.com/golang/protobuf/ptypes/any +411e09b969b1170a9f0c467558eb4c4c110d9c77 google.golang.org/genproto/googleapis/rpc/status +0eb507a2ca07f13baf499f89d66cc566bf644643 (dirty) google.golang.org/grpc/codes +0eb507a2ca07f13baf499f89d66cc566bf644643 (dirty) google.golang.org/grpc/status diff --git a/vendor/github.com/golang/protobuf/ptypes/any/any.pb.go b/vendor/github.com/golang/protobuf/ptypes/any/any.pb.go new file mode 100644 index 0000000..f2c6906 --- /dev/null +++ b/vendor/github.com/golang/protobuf/ptypes/any/any.pb.go @@ -0,0 +1,155 @@ +// Code generated by protoc-gen-go. +// source: github.com/golang/protobuf/ptypes/any/any.proto +// DO NOT EDIT! + +/* +Package any is a generated protocol buffer package. + +It is generated from these files: + github.com/golang/protobuf/ptypes/any/any.proto + +It has these top-level messages: + Any +*/ +package any + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// `Any` contains an arbitrary serialized protocol buffer message along with a +// URL that describes the type of the serialized message. +// +// Protobuf library provides support to pack/unpack Any values in the form +// of utility functions or additional generated methods of the Any type. +// +// Example 1: Pack and unpack a message in C++. +// +// Foo foo = ...; +// Any any; +// any.PackFrom(foo); +// ... +// if (any.UnpackTo(&foo)) { +// ... +// } +// +// Example 2: Pack and unpack a message in Java. +// +// Foo foo = ...; +// Any any = Any.pack(foo); +// ... +// if (any.is(Foo.class)) { +// foo = any.unpack(Foo.class); +// } +// +// Example 3: Pack and unpack a message in Python. +// +// foo = Foo(...) +// any = Any() +// any.Pack(foo) +// ... +// if any.Is(Foo.DESCRIPTOR): +// any.Unpack(foo) +// ... +// +// The pack methods provided by protobuf library will by default use +// 'type.googleapis.com/full.type.name' as the type URL and the unpack +// methods only use the fully qualified type name after the last '/' +// in the type URL, for example "foo.bar.com/x/y.z" will yield type +// name "y.z". +// +// +// JSON +// ==== +// The JSON representation of an `Any` value uses the regular +// representation of the deserialized, embedded message, with an +// additional field `@type` which contains the type URL. Example: +// +// package google.profile; +// message Person { +// string first_name = 1; +// string last_name = 2; +// } +// +// { +// "@type": "type.googleapis.com/google.profile.Person", +// "firstName": , +// "lastName": +// } +// +// If the embedded message type is well-known and has a custom JSON +// representation, that representation will be embedded adding a field +// `value` which holds the custom JSON in addition to the `@type` +// field. Example (for message [google.protobuf.Duration][]): +// +// { +// "@type": "type.googleapis.com/google.protobuf.Duration", +// "value": "1.212s" +// } +// +type Any struct { + // A URL/resource name whose content describes the type of the + // serialized protocol buffer message. + // + // For URLs which use the scheme `http`, `https`, or no scheme, the + // following restrictions and interpretations apply: + // + // * If no scheme is provided, `https` is assumed. + // * The last segment of the URL's path must represent the fully + // qualified name of the type (as in `path/google.protobuf.Duration`). + // The name should be in a canonical form (e.g., leading "." is + // not accepted). + // * An HTTP GET on the URL must yield a [google.protobuf.Type][] + // value in binary format, or produce an error. + // * Applications are allowed to cache lookup results based on the + // URL, or have them precompiled into a binary to avoid any + // lookup. Therefore, binary compatibility needs to be preserved + // on changes to types. (Use versioned type names to manage + // breaking changes.) + // + // Schemes other than `http`, `https` (or the empty scheme) might be + // used with implementation specific semantics. + // + TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl" json:"type_url,omitempty"` + // Must be a valid serialized protocol buffer of the above specified type. + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (m *Any) Reset() { *m = Any{} } +func (m *Any) String() string { return proto.CompactTextString(m) } +func (*Any) ProtoMessage() {} +func (*Any) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } +func (*Any) XXX_WellKnownType() string { return "Any" } + +func init() { + proto.RegisterType((*Any)(nil), "google.protobuf.Any") +} + +func init() { proto.RegisterFile("github.com/golang/protobuf/ptypes/any/any.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 187 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xd2, 0x4f, 0xcf, 0x2c, 0xc9, + 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xcf, 0xcf, 0x49, 0xcc, 0x4b, 0xd7, 0x2f, 0x28, + 0xca, 0x2f, 0xc9, 0x4f, 0x2a, 0x4d, 0xd3, 0x2f, 0x28, 0xa9, 0x2c, 0x48, 0x2d, 0xd6, 0x4f, 0xcc, + 0xab, 0x04, 0x61, 0x3d, 0xb0, 0xb8, 0x10, 0x7f, 0x7a, 0x7e, 0x7e, 0x7a, 0x4e, 0xaa, 0x1e, 0x4c, + 0x95, 0x92, 0x19, 0x17, 0xb3, 0x63, 0x5e, 0xa5, 0x90, 0x24, 0x17, 0x07, 0x48, 0x79, 0x7c, 0x69, + 0x51, 0x8e, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x67, 0x10, 0x3b, 0x88, 0x1f, 0x5a, 0x94, 0x23, 0x24, + 0xc2, 0xc5, 0x5a, 0x96, 0x98, 0x53, 0x9a, 0x2a, 0xc1, 0xa4, 0xc0, 0xa8, 0xc1, 0x13, 0x04, 0xe1, + 0x38, 0x15, 0x71, 0x09, 0x27, 0xe7, 0xe7, 0xea, 0xa1, 0x19, 0xe7, 0xc4, 0xe1, 0x98, 0x57, 0x19, + 0x00, 0xe2, 0x04, 0x30, 0x46, 0xa9, 0x12, 0xe5, 0xb8, 0x05, 0x8c, 0x8c, 0x8b, 0x98, 0x98, 0xdd, + 0x03, 0x9c, 0x56, 0x31, 0xc9, 0xb9, 0x43, 0x4c, 0x0b, 0x80, 0xaa, 0xd2, 0x0b, 0x4f, 0xcd, 0xc9, + 0xf1, 0xce, 0xcb, 0x2f, 0xcf, 0x0b, 0x01, 0xa9, 0x4e, 0x62, 0x03, 0x6b, 0x37, 0x06, 0x04, 0x00, + 0x00, 0xff, 0xff, 0xc6, 0x4d, 0x03, 0x23, 0xf6, 0x00, 0x00, 0x00, +} diff --git a/vendor/google.golang.org/genproto/googleapis/rpc/status/status.pb.go b/vendor/google.golang.org/genproto/googleapis/rpc/status/status.pb.go new file mode 100644 index 0000000..ec26060 --- /dev/null +++ b/vendor/google.golang.org/genproto/googleapis/rpc/status/status.pb.go @@ -0,0 +1,144 @@ +// Code generated by protoc-gen-go. +// source: google/rpc/status.proto +// DO NOT EDIT! + +/* +Package status is a generated protocol buffer package. + +It is generated from these files: + google/rpc/status.proto + +It has these top-level messages: + Status +*/ +package status + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import google_protobuf "github.com/golang/protobuf/ptypes/any" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// The `Status` type defines a logical error model that is suitable for different +// programming environments, including REST APIs and RPC APIs. It is used by +// [gRPC](https://github.com/grpc). The error model is designed to be: +// +// - Simple to use and understand for most users +// - Flexible enough to meet unexpected needs +// +// # Overview +// +// The `Status` message contains three pieces of data: error code, error message, +// and error details. The error code should be an enum value of +// [google.rpc.Code][google.rpc.Code], but it may accept additional error codes if needed. The +// error message should be a developer-facing English message that helps +// developers *understand* and *resolve* the error. If a localized user-facing +// error message is needed, put the localized message in the error details or +// localize it in the client. The optional error details may contain arbitrary +// information about the error. There is a predefined set of error detail types +// in the package `google.rpc` which can be used for common error conditions. +// +// # Language mapping +// +// The `Status` message is the logical representation of the error model, but it +// is not necessarily the actual wire format. When the `Status` message is +// exposed in different client libraries and different wire protocols, it can be +// mapped differently. For example, it will likely be mapped to some exceptions +// in Java, but more likely mapped to some error codes in C. +// +// # Other uses +// +// The error model and the `Status` message can be used in a variety of +// environments, either with or without APIs, to provide a +// consistent developer experience across different environments. +// +// Example uses of this error model include: +// +// - Partial errors. If a service needs to return partial errors to the client, +// it may embed the `Status` in the normal response to indicate the partial +// errors. +// +// - Workflow errors. A typical workflow has multiple steps. Each step may +// have a `Status` message for error reporting purpose. +// +// - Batch operations. If a client uses batch request and batch response, the +// `Status` message should be used directly inside batch response, one for +// each error sub-response. +// +// - Asynchronous operations. If an API call embeds asynchronous operation +// results in its response, the status of those operations should be +// represented directly using the `Status` message. +// +// - Logging. If some API errors are stored in logs, the message `Status` could +// be used directly after any stripping needed for security/privacy reasons. +type Status struct { + // The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + Code int32 `protobuf:"varint,1,opt,name=code" json:"code,omitempty"` + // A developer-facing error message, which should be in English. Any + // user-facing error message should be localized and sent in the + // [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + Message string `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` + // A list of messages that carry the error details. There will be a + // common set of message types for APIs to use. + Details []*google_protobuf.Any `protobuf:"bytes,3,rep,name=details" json:"details,omitempty"` +} + +func (m *Status) Reset() { *m = Status{} } +func (m *Status) String() string { return proto.CompactTextString(m) } +func (*Status) ProtoMessage() {} +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *Status) GetCode() int32 { + if m != nil { + return m.Code + } + return 0 +} + +func (m *Status) GetMessage() string { + if m != nil { + return m.Message + } + return "" +} + +func (m *Status) GetDetails() []*google_protobuf.Any { + if m != nil { + return m.Details + } + return nil +} + +func init() { + proto.RegisterType((*Status)(nil), "google.rpc.Status") +} + +func init() { proto.RegisterFile("google/rpc/status.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 209 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4f, 0xcf, 0xcf, 0x4f, + 0xcf, 0x49, 0xd5, 0x2f, 0x2a, 0x48, 0xd6, 0x2f, 0x2e, 0x49, 0x2c, 0x29, 0x2d, 0xd6, 0x2b, 0x28, + 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x82, 0x48, 0xe8, 0x15, 0x15, 0x24, 0x4b, 0x49, 0x42, 0x15, 0x81, + 0x65, 0x92, 0x4a, 0xd3, 0xf4, 0x13, 0xf3, 0x2a, 0x21, 0xca, 0x94, 0xd2, 0xb8, 0xd8, 0x82, 0xc1, + 0xda, 0x84, 0x84, 0xb8, 0x58, 0x92, 0xf3, 0x53, 0x52, 0x25, 0x18, 0x15, 0x18, 0x35, 0x58, 0x83, + 0xc0, 0x6c, 0x21, 0x09, 0x2e, 0xf6, 0xdc, 0xd4, 0xe2, 0xe2, 0xc4, 0xf4, 0x54, 0x09, 0x26, 0x05, + 0x46, 0x0d, 0xce, 0x20, 0x18, 0x57, 0x48, 0x8f, 0x8b, 0x3d, 0x25, 0xb5, 0x24, 0x31, 0x33, 0xa7, + 0x58, 0x82, 0x59, 0x81, 0x59, 0x83, 0xdb, 0x48, 0x44, 0x0f, 0x6a, 0x21, 0xcc, 0x12, 0x3d, 0xc7, + 0xbc, 0xca, 0x20, 0x98, 0x22, 0xa7, 0x38, 0x2e, 0xbe, 0xe4, 0xfc, 0x5c, 0x3d, 0x84, 0xa3, 0x9c, + 0xb8, 0x21, 0xf6, 0x06, 0x80, 0x94, 0x07, 0x30, 0x46, 0x99, 0x43, 0xa5, 0xd2, 0xf3, 0x73, 0x12, + 0xf3, 0xd2, 0xf5, 0xf2, 0x8b, 0xd2, 0xf5, 0xd3, 0x53, 0xf3, 0xc0, 0x86, 0xe9, 0x43, 0xa4, 0x12, + 0x0b, 0x32, 0x8b, 0x91, 0xfc, 0x69, 0x0d, 0xa1, 0x16, 0x31, 0x31, 0x07, 0x05, 0x38, 0x27, 0xb1, + 0x81, 0x55, 0x1a, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xa4, 0x53, 0xf0, 0x7c, 0x10, 0x01, 0x00, + 0x00, +} diff --git a/vendor/google.golang.org/grpc/status/status.go b/vendor/google.golang.org/grpc/status/status.go new file mode 100644 index 0000000..99a4cbe --- /dev/null +++ b/vendor/google.golang.org/grpc/status/status.go @@ -0,0 +1,145 @@ +/* + * + * Copyright 2017, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +// Package status implements errors returned by gRPC. These errors are +// serialized and transmitted on the wire between server and client, and allow +// for additional data to be transmitted via the Details field in the status +// proto. gRPC service handlers should return an error created by this +// package, and gRPC clients should expect a corresponding error to be +// returned from the RPC call. +// +// This package upholds the invariants that a non-nil error may not +// contain an OK code, and an OK code must result in a nil error. +package status + +import ( + "fmt" + + "github.com/golang/protobuf/proto" + spb "google.golang.org/genproto/googleapis/rpc/status" + "google.golang.org/grpc/codes" +) + +// statusError is an alias of a status proto. It implements error and Status, +// and a nil statusError should never be returned by this package. +type statusError spb.Status + +func (se *statusError) Error() string { + p := (*spb.Status)(se) + return fmt.Sprintf("rpc error: code = %s desc = %s", codes.Code(p.GetCode()), p.GetMessage()) +} + +func (se *statusError) status() *Status { + return &Status{s: (*spb.Status)(se)} +} + +// Status represents an RPC status code, message, and details. It is immutable +// and should be created with New, Newf, or FromProto. +type Status struct { + s *spb.Status +} + +// Code returns the status code contained in s. +func (s *Status) Code() codes.Code { + if s == nil || s.s == nil { + return codes.OK + } + return codes.Code(s.s.Code) +} + +// Message returns the message contained in s. +func (s *Status) Message() string { + if s == nil || s.s == nil { + return "" + } + return s.s.Message +} + +// Proto returns s's status as an spb.Status proto message. +func (s *Status) Proto() *spb.Status { + if s == nil { + return nil + } + return proto.Clone(s.s).(*spb.Status) +} + +// Err returns an immutable error representing s; returns nil if s.Code() is +// OK. +func (s *Status) Err() error { + if s.Code() == codes.OK { + return nil + } + return (*statusError)(s.s) +} + +// New returns a Status representing c and msg. +func New(c codes.Code, msg string) *Status { + return &Status{s: &spb.Status{Code: int32(c), Message: msg}} +} + +// Newf returns New(c, fmt.Sprintf(format, a...)). +func Newf(c codes.Code, format string, a ...interface{}) *Status { + return New(c, fmt.Sprintf(format, a...)) +} + +// Error returns an error representing c and msg. If c is OK, returns nil. +func Error(c codes.Code, msg string) error { + return New(c, msg).Err() +} + +// Errorf returns Error(c, fmt.Sprintf(format, a...)). +func Errorf(c codes.Code, format string, a ...interface{}) error { + return Error(c, fmt.Sprintf(format, a...)) +} + +// ErrorProto returns an error representing s. If s.Code is OK, returns nil. +func ErrorProto(s *spb.Status) error { + return FromProto(s).Err() +} + +// FromProto returns a Status representing s. +func FromProto(s *spb.Status) *Status { + return &Status{s: proto.Clone(s).(*spb.Status)} +} + +// FromError returns a Status representing err if it was produced from this +// package, otherwise it returns nil, false. +func FromError(err error) (s *Status, ok bool) { + if err == nil { + return &Status{s: &spb.Status{Code: int32(codes.OK)}}, true + } + if s, ok := err.(*statusError); ok { + return s.status(), true + } + return nil, false +}