diff --git a/cmd/things/main.go b/cmd/things/main.go index bebd5346..25fb90c8 100644 --- a/cmd/things/main.go +++ b/cmd/things/main.go @@ -30,6 +30,7 @@ import ( httpapi "github.com/mainflux/mainflux/things/api/http" "github.com/mainflux/mainflux/things/postgres" rediscache "github.com/mainflux/mainflux/things/redis" + localusers "github.com/mainflux/mainflux/things/users" "github.com/mainflux/mainflux/things/uuid" usersapi "github.com/mainflux/mainflux/users/api/grpc" stdprometheus "github.com/prometheus/client_golang/prometheus" @@ -37,70 +38,77 @@ import ( ) const ( - defLogLevel = "error" - defDBHost = "localhost" - defDBPort = "5432" - defDBUser = "mainflux" - defDBPass = "mainflux" - defDBName = "things" - defDBSSLMode = "disable" - defDBSSLCert = "" - defDBSSLKey = "" - defDBSSLRootCert = "" - defClientTLS = "false" - defCACerts = "" - defCacheURL = "localhost:6379" - defCachePass = "" - defCacheDB = "0" - defESURL = "localhost:6379" - defESPass = "" - defESDB = "0" - defHTTPPort = "8180" - defGRPCPort = "8181" - defServerCert = "" - defServerKey = "" - defUsersURL = "localhost:8181" - envLogLevel = "MF_THINGS_LOG_LEVEL" - envDBHost = "MF_THINGS_DB_HOST" - envDBPort = "MF_THINGS_DB_PORT" - envDBUser = "MF_THINGS_DB_USER" - envDBPass = "MF_THINGS_DB_PASS" - envDBName = "MF_THINGS_DB" - envDBSSLMode = "MF_THINGS_DB_SSL_MODE" - envDBSSLCert = "MF_THINGS_DB_SSL_CERT" - envDBSSLKey = "MF_THINGS_DB_SSL_KEY" - envDBSSLRootCert = "MF_THINGS_DB_SSL_ROOT_CERT" - envClientTLS = "MF_THINGS_CLIENT_TLS" - envCACerts = "MF_THINGS_CA_CERTS" - envCacheURL = "MF_THINGS_CACHE_URL" - envCachePass = "MF_THINGS_CACHE_PASS" - envCacheDB = "MF_THINGS_CACHE_DB" - envESURL = "MF_THINGS_ES_URL" - envESPass = "MF_THINGS_ES_PASS" - envESDB = "MF_THINGS_ES_DB" - envHTTPPort = "MF_THINGS_HTTP_PORT" - envGRPCPort = "MF_THINGS_GRPC_PORT" - envUsersURL = "MF_USERS_URL" - envServerCert = "MF_THINGS_SERVER_CERT" - envServerKey = "MF_THINGS_SERVER_KEY" + defLogLevel = "error" + defDBHost = "localhost" + defDBPort = "5432" + defDBUser = "mainflux" + defDBPass = "mainflux" + defDBName = "things" + defDBSSLMode = "disable" + defDBSSLCert = "" + defDBSSLKey = "" + defDBSSLRootCert = "" + defClientTLS = "false" + defCACerts = "" + defCacheURL = "localhost:6379" + defCachePass = "" + defCacheDB = "0" + defESURL = "localhost:6379" + defESPass = "" + defESDB = "0" + defHTTPPort = "8180" + defGRPCPort = "8181" + defServerCert = "" + defServerKey = "" + defUsersURL = "localhost:8181" + defSingleUserEmail = "" + defSingleUserToken = "" + + envLogLevel = "MF_THINGS_LOG_LEVEL" + envDBHost = "MF_THINGS_DB_HOST" + envDBPort = "MF_THINGS_DB_PORT" + envDBUser = "MF_THINGS_DB_USER" + envDBPass = "MF_THINGS_DB_PASS" + envDBName = "MF_THINGS_DB" + envDBSSLMode = "MF_THINGS_DB_SSL_MODE" + envDBSSLCert = "MF_THINGS_DB_SSL_CERT" + envDBSSLKey = "MF_THINGS_DB_SSL_KEY" + envDBSSLRootCert = "MF_THINGS_DB_SSL_ROOT_CERT" + envClientTLS = "MF_THINGS_CLIENT_TLS" + envCACerts = "MF_THINGS_CA_CERTS" + envCacheURL = "MF_THINGS_CACHE_URL" + envCachePass = "MF_THINGS_CACHE_PASS" + envCacheDB = "MF_THINGS_CACHE_DB" + envESURL = "MF_THINGS_ES_URL" + envESPass = "MF_THINGS_ES_PASS" + envESDB = "MF_THINGS_ES_DB" + envHTTPPort = "MF_THINGS_HTTP_PORT" + envGRPCPort = "MF_THINGS_GRPC_PORT" + envUsersURL = "MF_USERS_URL" + envServerCert = "MF_THINGS_SERVER_CERT" + envServerKey = "MF_THINGS_SERVER_KEY" + envSingleUserEmail = "MF_THINGS_SINGLE_USER_EMAIL" + envSingleUserToken = "MF_THINGS_SINGLE_USER_TOKEN" ) type config struct { - logLevel string - dbConfig postgres.Config - clientTLS bool - caCerts string - cacheURL string - cachePass string - cacheDB string - esURL string - esPass string - esDB string - httpPort string - grpcPort string - usersURL string - serverCert string - serverKey string + logLevel string + dbConfig postgres.Config + clientTLS bool + caCerts string + cacheURL string + cachePass string + cacheDB string + esURL string + esPass string + esDB string + httpPort string + grpcPort string + usersURL string + serverCert string + serverKey string + singleUserEmail string + singleUserToken string } func main() { @@ -117,10 +125,12 @@ func main() { db := connectToDB(cfg.dbConfig, logger) defer db.Close() - conn := connectToUsers(cfg, logger) - defer conn.Close() + users, close := createUsersClient(cfg, logger) + if close != nil { + defer close() + } - svc := newService(conn, db, cacheClient, esClient, logger) + svc := newService(users, db, cacheClient, esClient, logger) errs := make(chan error, 2) go startHTTPServer(svc, cfg, logger, errs) @@ -155,21 +165,23 @@ func loadConfig() config { } return config{ - logLevel: mainflux.Env(envLogLevel, defLogLevel), - dbConfig: dbConfig, - clientTLS: tls, - caCerts: mainflux.Env(envCACerts, defCACerts), - cacheURL: mainflux.Env(envCacheURL, defCacheURL), - cachePass: mainflux.Env(envCachePass, defCachePass), - cacheDB: mainflux.Env(envCacheDB, defCacheDB), - esURL: mainflux.Env(envESURL, defESURL), - esPass: mainflux.Env(envESPass, defESPass), - esDB: mainflux.Env(envESDB, defESDB), - httpPort: mainflux.Env(envHTTPPort, defHTTPPort), - grpcPort: mainflux.Env(envGRPCPort, defGRPCPort), - usersURL: mainflux.Env(envUsersURL, defUsersURL), - serverCert: mainflux.Env(envServerCert, defServerCert), - serverKey: mainflux.Env(envServerKey, defServerKey), + logLevel: mainflux.Env(envLogLevel, defLogLevel), + dbConfig: dbConfig, + clientTLS: tls, + caCerts: mainflux.Env(envCACerts, defCACerts), + cacheURL: mainflux.Env(envCacheURL, defCacheURL), + cachePass: mainflux.Env(envCachePass, defCachePass), + cacheDB: mainflux.Env(envCacheDB, defCacheDB), + esURL: mainflux.Env(envESURL, defESURL), + esPass: mainflux.Env(envESPass, defESPass), + esDB: mainflux.Env(envESDB, defESDB), + httpPort: mainflux.Env(envHTTPPort, defHTTPPort), + grpcPort: mainflux.Env(envGRPCPort, defGRPCPort), + usersURL: mainflux.Env(envUsersURL, defUsersURL), + serverCert: mainflux.Env(envServerCert, defServerCert), + serverKey: mainflux.Env(envServerKey, defServerKey), + singleUserEmail: mainflux.Env(envSingleUserEmail, defSingleUserEmail), + singleUserToken: mainflux.Env(envSingleUserToken, defSingleUserToken), } } @@ -196,6 +208,15 @@ func connectToDB(dbConfig postgres.Config, logger logger.Logger) *sqlx.DB { return db } +func createUsersClient(cfg config, logger logger.Logger) (mainflux.UsersServiceClient, func() error) { + if cfg.singleUserEmail != "" && cfg.singleUserToken != "" { + return localusers.NewSingleUserService(cfg.singleUserEmail, cfg.singleUserToken), nil + } + + conn := connectToUsers(cfg, logger) + return usersapi.NewClient(conn), conn.Close +} + func connectToUsers(cfg config, logger logger.Logger) *grpc.ClientConn { var opts []grpc.DialOption if cfg.clientTLS { @@ -221,8 +242,7 @@ func connectToUsers(cfg config, logger logger.Logger) *grpc.ClientConn { return conn } -func newService(conn *grpc.ClientConn, db *sqlx.DB, cacheClient *redis.Client, esClient *redis.Client, logger logger.Logger) things.Service { - users := usersapi.NewClient(conn) +func newService(users mainflux.UsersServiceClient, db *sqlx.DB, cacheClient *redis.Client, esClient *redis.Client, logger logger.Logger) things.Service { thingsRepo := postgres.NewThingRepository(db) channelsRepo := postgres.NewChannelRepository(db) chanCache := rediscache.NewChannelCache(cacheClient) diff --git a/things/README.md b/things/README.md index d22d8bd2..eee38293 100644 --- a/things/README.md +++ b/things/README.md @@ -16,31 +16,35 @@ The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values. -| Variable | Description | Default | -|----------------------------|------------------------------------------------------------------------|----------------| -| MF_THINGS_LOG_LEVEL | Log level for Things (debug, info, warn, error) | error | -| MF_THINGS_DB_HOST | Database host address | localhost | -| MF_THINGS_DB_PORT | Database host port | 5432 | -| MF_THINGS_DB_USER | Database user | mainflux | -| MF_THINGS_DB_PASS | Database password | mainflux | -| MF_THINGS_DB | Name of the database used by the service | things | -| MF_THINGS_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full)| disable | -| MF_THINGS_DB_SSL_CERT | Path to the PEM encoded certificate file | | -| MF_THINGS_DB_SSL_KEY | Path to the PEM encoded key file | | -| MF_THINGS_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | | -| MF_THINGS_CLIENT_TLS | Flag that indicates if TLS should be turned on | false | -| MF_THINGS_CA_CERTS | Path to trusted CAs in PEM format | | -| MF_THINGS_CACHE_URL | Cache database URL | localhost:6379 | -| MF_THINGS_CACHE_PASS | Cache database password | | -| MF_THINGS_CACHE_DB | Cache instance that should be used | 0 | -| MF_THINGS_ES_URL | Event store URL | localhost:6379 | -| MF_THINGS_ES_PASS | Event store password | | -| MF_THINGS_ES_DB | Event store instance that should be used | 0 | -| MF_THINGS_HTTP_PORT | Things service HTTP port | 8180 | -| MF_THINGS_GRPC_PORT | Things service gRPC port | 8181 | -| MF_THINGS_SERVER_CERT | Path to server certificate in pem format | 8181 | -| MF_THINGS_SERVER_KEY | Path to server key in pem format | 8181 | -| MF_USERS_URL | Users service URL | localhost:8181 | +| Variable | Description | Default | +|-----------------------------|------------------------------------------------------------------------|----------------| +| MF_THINGS_LOG_LEVEL | Log level for Things (debug, info, warn, error) | error | +| MF_THINGS_DB_HOST | Database host address | localhost | +| MF_THINGS_DB_PORT | Database host port | 5432 | +| MF_THINGS_DB_USER | Database user | mainflux | +| MF_THINGS_DB_PASS | Database password | mainflux | +| MF_THINGS_DB | Name of the database used by the service | things | +| MF_THINGS_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full)| disable | +| MF_THINGS_DB_SSL_CERT | Path to the PEM encoded certificate file | | +| MF_THINGS_DB_SSL_KEY | Path to the PEM encoded key file | | +| MF_THINGS_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | | +| MF_THINGS_CLIENT_TLS | Flag that indicates if TLS should be turned on | false | +| MF_THINGS_CA_CERTS | Path to trusted CAs in PEM format | | +| MF_THINGS_CACHE_URL | Cache database URL | localhost:6379 | +| MF_THINGS_CACHE_PASS | Cache database password | | +| MF_THINGS_CACHE_DB | Cache instance that should be used | 0 | +| MF_THINGS_ES_URL | Event store URL | localhost:6379 | +| MF_THINGS_ES_PASS | Event store password | | +| MF_THINGS_ES_DB | Event store instance that should be used | 0 | +| MF_THINGS_HTTP_PORT | Things service HTTP port | 8180 | +| MF_THINGS_GRPC_PORT | Things service gRPC port | 8181 | +| MF_THINGS_SERVER_CERT | Path to server certificate in pem format | 8181 | +| MF_THINGS_SERVER_KEY | Path to server key in pem format | 8181 | +| MF_USERS_URL | Users service URL | localhost:8181 | +| MF_THINGS_SINGLE_USER_EMAIL | User email for single user mode (no gRPC communication with users) | | +| MF_THINGS_SINGLE_USER_TOKEN | User token for single user mode that should be passed in auth header | | + +**Note** that if you want `things` service to have only one user locally, you should use `MF_THINGS_SINGLE_USER` env vars. By specifying these, you don't need `users` service in your deployment as it won't be used for authorization. ## Deployment @@ -80,6 +84,8 @@ services: MF_THINGS_SERVER_KEY: [String path to server key in pem format] MF_USERS_URL: [Users service URL] MF_THINGS_SECRET: [String used for signing tokens] + MF_THINGS_SINGLE_USER_EMAIL: [User email for single user mode (no gRPC communication with users)] + MF_THINGS_SINGLE_USER_TOKEN: [User token for single user mode that should be passed in auth header] ``` To start the service outside of the container, execute the following shell script: @@ -97,7 +103,7 @@ make things make install # set the environment variables and run the service -MF_THINGS_LOG_LEVEL=[Things log level] MF_THINGS_DB_HOST=[Database host address] MF_THINGS_DB_PORT=[Database host port] MF_THINGS_DB_USER=[Database user] MF_THINGS_DB_PASS=[Database password] MF_THINGS_DB=[Name of the database used by the service] MF_THINGS_DB_SSL_MODE=[SSL mode to connect to the database with] MF_THINGS_DB_SSL_CERT=[Path to the PEM encoded certificate file] MF_THINGS_DB_SSL_KEY=[Path to the PEM encoded key file] MF_THINGS_DB_SSL_ROOT_CERT=[Path to the PEM encoded root certificate file] MF_HTTP_ADAPTER_CA_CERTS=[Path to trusted CAs in PEM format] MF_THINGS_CACHE_URL=[Cache database URL] MF_THINGS_CACHE_PASS=[Cache database password] MF_THINGS_CACHE_DB=[Cache instance that should be used] MF_THINGS_ES_URL=[Event store URL] MF_THINGS_ES_PASS=[Event store password] MF_THINGS_ES_DB=[Event store instance that should be used] MF_THINGS_HTTP_PORT=[Service HTTP port] MF_THINGS_GRPC_PORT=[Service gRPC port] MF_USERS_URL=[Users service URL] MF_THINGS_SERVER_CERT=[Path to server certificate] MF_THINGS_SERVER_KEY=[Path to server key] $GOBIN/mainflux-things +MF_THINGS_LOG_LEVEL=[Things log level] MF_THINGS_DB_HOST=[Database host address] MF_THINGS_DB_PORT=[Database host port] MF_THINGS_DB_USER=[Database user] MF_THINGS_DB_PASS=[Database password] MF_THINGS_DB=[Name of the database used by the service] MF_THINGS_DB_SSL_MODE=[SSL mode to connect to the database with] MF_THINGS_DB_SSL_CERT=[Path to the PEM encoded certificate file] MF_THINGS_DB_SSL_KEY=[Path to the PEM encoded key file] MF_THINGS_DB_SSL_ROOT_CERT=[Path to the PEM encoded root certificate file] MF_HTTP_ADAPTER_CA_CERTS=[Path to trusted CAs in PEM format] MF_THINGS_CACHE_URL=[Cache database URL] MF_THINGS_CACHE_PASS=[Cache database password] MF_THINGS_CACHE_DB=[Cache instance that should be used] MF_THINGS_ES_URL=[Event store URL] MF_THINGS_ES_PASS=[Event store password] MF_THINGS_ES_DB=[Event store instance that should be used] MF_THINGS_HTTP_PORT=[Service HTTP port] MF_THINGS_GRPC_PORT=[Service gRPC port] MF_USERS_URL=[Users service URL] MF_THINGS_SERVER_CERT=[Path to server certificate] MF_THINGS_SERVER_KEY=[Path to server key] MF_THINGS_SINGLE_USER_EMAIL=[User email for single user mode (no gRPC communication with users)] MF_THINGS_SINGLE_USER_TOKEN=[User token for single user mode that should be passed in auth header] $GOBIN/mainflux-things ``` Setting `MF_THINGS_CA_CERTS` expects a file in PEM format of trusted CAs. This will enable TLS against the Users gRPC endpoint trusting only those CAs that are provided. diff --git a/things/users/users.go b/things/users/users.go new file mode 100644 index 00000000..10ad6b0f --- /dev/null +++ b/things/users/users.go @@ -0,0 +1,42 @@ +// +// Copyright (c) 2019 +// Mainflux +// +// SPDX-License-Identifier: Apache-2.0 +// + +// Package users contains implementation for users service in +// single user scenario. +package users + +import ( + "context" + + "github.com/mainflux/mainflux/things" + + "github.com/mainflux/mainflux" + "google.golang.org/grpc" +) + +var _ mainflux.UsersServiceClient = (*singleUserRepo)(nil) + +type singleUserRepo struct { + email string + token string +} + +// NewSingleUserService creates single user repository for constraind environments. +func NewSingleUserService(email, token string) mainflux.UsersServiceClient { + return singleUserRepo{ + email: email, + token: token, + } +} + +func (repo singleUserRepo) Identify(_ context.Context, token *mainflux.Token, opts ...grpc.CallOption) (*mainflux.UserID, error) { + if repo.token != token.GetValue() { + return nil, things.ErrUnauthorizedAccess + } + + return &mainflux.UserID{Value: repo.email}, nil +} diff --git a/things/users/users_test.go b/things/users/users_test.go new file mode 100644 index 00000000..56e54060 --- /dev/null +++ b/things/users/users_test.go @@ -0,0 +1,51 @@ +// +// Copyright (c) 2019 +// Mainflux +// +// SPDX-License-Identifier: Apache-2.0 +// + +package users_test + +import ( + "context" + "fmt" + "testing" + + "github.com/mainflux/mainflux" + "github.com/mainflux/mainflux/things" + "github.com/mainflux/mainflux/things/users" + "github.com/stretchr/testify/assert" +) + +const ( + email = "john.doe@example.com" + token = "token" +) + +func TestIdentify(t *testing.T) { + svc := users.NewSingleUserService(email, token) + + cases := map[string]struct { + token string + id string + err error + }{ + "identify non-existing user": { + token: "non-existing", + id: "", + err: things.ErrUnauthorizedAccess, + }, + "identify existing user": { + token: token, + id: email, + err: nil, + }, + } + + for desc, tc := range cases { + id, err := svc.Identify(context.Background(), &mainflux.Token{Value: tc.token}) + assert.Equal(t, tc.id, id.GetValue(), fmt.Sprintf("%s: expected %s, got %s", desc, tc.id, id.GetValue())) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s, got %s", desc, tc.err, err)) + } +}