Merge branch 'master' into backend-error
This commit is contained in:
commit
4be4a085d8
|
@ -21,8 +21,12 @@ RUN tar xzvf mosquitto-${MOSQUITTO_VERSION}.tar.gz && rm mosquitto-${MOSQUITTO_V
|
|||
RUN cd mosquitto-${MOSQUITTO_VERSION} && make WITH_WEBSOCKETS=yes && make install && cd ..
|
||||
|
||||
#Get Go.
|
||||
RUN wget https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz && tar -C /usr/local -xzf go${GO_VERSION}.linux-amd64.tar.gz
|
||||
RUN export PATH=$PATH:/usr/local/go/bin && go version && rm go${GO_VERSION}.linux-amd64.tar.gz
|
||||
RUN export GO_ARCH=$(uname -m | sed -es/x86_64/amd64/ -es/armv7l/armv6l/ -es/aarch64/arm64/) && \
|
||||
wget https://dl.google.com/go/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz && \
|
||||
tar -C /usr/local -xzf go${GO_VERSION}.linux-${GO_ARCH}.tar.gz && \
|
||||
export PATH=$PATH:/usr/local/go/bin && \
|
||||
go version && \
|
||||
rm go${GO_VERSION}.linux-${GO_ARCH}.tar.gz
|
||||
|
||||
#Build the plugin from local source
|
||||
COPY ./ ./
|
||||
|
|
74
Gopkg.toml
74
Gopkg.toml
|
@ -1,74 +0,0 @@
|
|||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||
# 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"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/dgrijalva/jwt-go"
|
||||
version = "3.2.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-redis/redis"
|
||||
version = "6.14.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-sql-driver/mysql"
|
||||
version = "1.4.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/jmoiron/sqlx"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/lib/pq"
|
||||
version = "1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mattn/go-sqlite3"
|
||||
version = "1.9.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/pkg/errors"
|
||||
version = "0.8.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/sirupsen/logrus"
|
||||
version = "1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/smartystreets/goconvey"
|
||||
version = "1.6.3"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
|
||||
[[constraint]]
|
||||
branch = "v2"
|
||||
name = "gopkg.in/mgo.v2"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
37
Makefile
37
Makefile
|
@ -1,18 +1,35 @@
|
|||
CFLAGS := -I/usr/local/include -fPIC
|
||||
LDFLAGS := -shared
|
||||
|
||||
UNAME_S := $(shell uname -s)
|
||||
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
LDFLAGS += -undefined dynamic_lookup
|
||||
endif
|
||||
|
||||
all:
|
||||
go build -buildmode=c-archive go-auth.go
|
||||
go build -buildmode=c-shared -o go-auth.so
|
||||
@echo "Bulding for $(UNAME_S)"
|
||||
env CGO_CFLAGS="$(CFLAGS)" go build -buildmode=c-archive go-auth.go
|
||||
env CGO_LDFLAGS="$(LDFLAGS)" go build -buildmode=c-shared -o go-auth.so
|
||||
go build pw-gen/pw.go
|
||||
|
||||
dev-requirements:
|
||||
go get -u github.com/golang/dep/cmd/dep
|
||||
go get -u github.com/smartystreets/goconvey
|
||||
|
||||
test:
|
||||
go test ./backends ./cache ./hashing -v -bench=none -count=1
|
||||
go test ./backends ./cache ./hashing -v -count=1
|
||||
|
||||
benchmark:
|
||||
go test ./backends -v -bench=. -run=^a
|
||||
test-backends:
|
||||
go test ./backends -v -failfast -count=1
|
||||
|
||||
test-cache:
|
||||
go test ./cache -v -failfast -count=1
|
||||
|
||||
test-hashing:
|
||||
go test ./hashing -v -failfast -count=1
|
||||
|
||||
service:
|
||||
@echo "Generating gRPC code from .proto files"
|
||||
@go generate grpc/grpc.go
|
||||
@go generate grpc/grpc.go
|
||||
|
||||
clean:
|
||||
rm -f go-auth.h
|
||||
rm -f go-auth.so
|
||||
rm -f pw
|
368
README.md
368
README.md
|
@ -2,6 +2,18 @@
|
|||
|
||||
Mosquitto Go Auth is an authentication and authorization plugin for the Mosquitto MQTT broker.
|
||||
|
||||
# Current state
|
||||
|
||||
I don't use Mosquitto or any other MQTT broker and haven't in a very long time, nor do I have a need for them or this plugin.
|
||||
I do maintain it still and will try to keep doing so. This is the list of status, current work and priorities:
|
||||
|
||||
- The plugin is up to date and is compatible with the recent [2.0 Mosquitto version](https://mosquitto.org/blog/2020/12/version-2-0-0-released/).
|
||||
- Delayed work on disabling superusers is not yet ready.
|
||||
- Bug reports will be attended as they appear and will take priority over any work in progress.
|
||||
- Reviewing ongoing PRs is my next priority.
|
||||
- Feature requests are the lowest priority. Unless they are a super easy win in importance and implementation effort, I'll accept contributions and review
|
||||
PRs before considering implementing them myself.
|
||||
|
||||
### Intro
|
||||
|
||||
This is an authentication and authorization plugin for [mosquitto](https://mosquitto.org/), a well known open source MQTT broker. It's written (almost) entirely in Go: it uses `cgo` to expose mosquitto's auth plugin needed functions, but internally just calls Go to get everything done.
|
||||
|
@ -20,6 +32,7 @@ These are the backends that this plugin implements right now:
|
|||
* MongoDB
|
||||
* Custom (experimental)
|
||||
* gRPC
|
||||
* Javascript interpreter
|
||||
|
||||
**Every backend offers user, superuser and acl checks, and include proper tests.**
|
||||
|
||||
|
@ -52,6 +65,7 @@ Please open an issue with the `feature` or `enhancement` tag to request new back
|
|||
- [JWT](#jwt)
|
||||
- [Remote mode](#remote-mode)
|
||||
- [Local mode](#local-mode)
|
||||
- [JS mode](#js-mode)
|
||||
- [Testing JWT](#testing-jwt)
|
||||
- [HTTP](#http)
|
||||
- [Response mode](#response-mode)
|
||||
|
@ -66,6 +80,8 @@ Please open an issue with the `feature` or `enhancement` tag to request new back
|
|||
- [gRPC](#grpc)
|
||||
- [Service](#service)
|
||||
- [Testing gRPC](#testing-grpc)
|
||||
- [Javascript](#javascript)
|
||||
- [Testing Javascript](#testing-javascript)
|
||||
- [Using with LoRa Server](#using-with-lora-server)
|
||||
- [Docker](#docker)
|
||||
- [License](#license)
|
||||
|
@ -76,7 +92,7 @@ Please open an issue with the `feature` or `enhancement` tag to request new back
|
|||
|
||||
### Requirements
|
||||
|
||||
This package uses `Go modules` to manage dependencies, `dep` is no longer supported.
|
||||
This package uses `Go modules` to manage dependencies.
|
||||
As it interacts with `mosquitto`, it makes use of `cgo`. Also, it (optionally) uses Redis for cache purposes.
|
||||
|
||||
|
||||
|
@ -130,49 +146,19 @@ WantedBy=multi-user.target
|
|||
|
||||
If you are running another distro or need more details on building mosquitto, please check the offical mosquitto docs.
|
||||
|
||||
#### Build the plugin for mosquitto 1.4.x
|
||||
#### Building the plugin
|
||||
|
||||
Now that mosquitto is installed, building the project is fairly simple given that you meet the requirements. Just run this commands to generate go-auth.h and then go-auth.so:
|
||||
|
||||
```
|
||||
go build -buildmode=c-archive go-auth.go
|
||||
go build -buildmode=c-shared -o go-auth.so
|
||||
```
|
||||
|
||||
or simply:
|
||||
Only Linux (tested in Debian, Ubuntu and Mint versions) and MacOS are supported. This will build `go-auth.so` shared object:
|
||||
|
||||
```
|
||||
make
|
||||
```
|
||||
|
||||
You can also run all tests (see Testing X for each backend's testing requirements) like this:
|
||||
This assumes that `mosquitto.h`, `mosquitto_plugin.h` and `mosquitto_broker.h` are located at `/usr/local/include`, which is true for a manually built `mosquitto` version in debian based systems (and probably others too), or manually built or installed through brew (```brew install mosquitto```) `mosquitto` version in MacOS.
|
||||
|
||||
```
|
||||
make test
|
||||
```
|
||||
If this doesn't work for your distribution or OS version, please check `Makefile` `CFLAGS` and `LDFLAGS` and adjust accordingly.
|
||||
File an issue or open a PR if you wish to contribute correct flags for your system.
|
||||
|
||||
#### Build the plugin for mosquitto 1.5.x and 1.6.x
|
||||
|
||||
For the latest versions of mosquitto we need to export some flags before building and then run the same commands (we'll just use make):
|
||||
|
||||
##### Debian (and maybe others)
|
||||
```
|
||||
export CGO_CFLAGS="-I/usr/local/include -fPIC"
|
||||
export CGO_LDFLAGS="-shared"
|
||||
make
|
||||
```
|
||||
|
||||
This assumes that `mosquitto.h`, `mosquitto_plugin.h` and `mosquitto_broker.h` are located at `/usr/local/include`, which is true for a manually built mosquitto version in debian based systems (and probably others too).
|
||||
|
||||
##### MacOS
|
||||
```
|
||||
export CGO_CFLAGS="-I/usr/local/include -fPIC"
|
||||
export CGO_LDFLAGS="-undefined dynamic_lookup"
|
||||
export CGO_LDFLAGS="-shared"
|
||||
make
|
||||
```
|
||||
|
||||
This assumes that `mosquitto.h`, `mosquitto_plugin.h` and `mosquitto_broker.h` are located at `/usr/local/include`, which is true for a manually built or using brew (```brew install mosquitto```) to install mosquitto version in MacOS.
|
||||
|
||||
#### Raspberry Pi
|
||||
|
||||
|
@ -227,25 +213,23 @@ make install
|
|||
|
||||
### Configuration
|
||||
|
||||
The plugin is configured in [Mosquitto's](https://mosquitto.org/) configuration file (typically `mosquitto.conf`),
|
||||
and it is loaded into Mosquitto auth with the ```auth_plugin``` option.
|
||||
|
||||
|
||||
#### General options
|
||||
|
||||
Set path to plugin and include conf.d dir for further configuration:
|
||||
The plugin is configured in [Mosquitto's](https://mosquitto.org/) configuration file (typically `mosquitto.conf`).
|
||||
You may define all options there, or include e.g. a `conf.d` dir for plugin configuration:
|
||||
|
||||
```
|
||||
auth_plugin /path/to/go-auth.so
|
||||
include_dir /etc/mosquitto/conf.d
|
||||
```
|
||||
|
||||
Create some conf file (e.g., mosquitto-go-auth.conf) at /etc/mosquitto/conf.d/ and register the desired backends with:
|
||||
Create some conf file (e.g., `go-auth.conf`) at your preferred location, e.g. `/etc/mosquitto/conf.d/`, and register the plugin's shared object path and desired backends with:
|
||||
|
||||
```
|
||||
auth_plugin /etc/mosquitto/conf.d/go-auth.so
|
||||
|
||||
auth_opt_backends files, postgres, jwt
|
||||
```
|
||||
|
||||
Set all other plugin options below in the same file.
|
||||
|
||||
#### Cache
|
||||
|
||||
There are 2 types of caches supported: an in memory one using [go-cache](https://github.com/patrickmn/go-cache), or a Redis backed one.
|
||||
|
@ -433,6 +417,13 @@ As of now every backend has proper but really ugly tests in place: they expect s
|
|||
This issue captures these concerns and a basic plan to refactor tests: https://github.com/iegomez/mosquitto-go-auth/issues/67.
|
||||
|
||||
|
||||
You may run all tests (see Testing X for each backend's testing requirements) like this:
|
||||
|
||||
```
|
||||
make test
|
||||
```
|
||||
|
||||
|
||||
### Files
|
||||
|
||||
The `files` backend implements the regular password and acl checks as described in mosquitto. Passwords should be in `PBKDF2`, `Bcrypt` or `Argon2ID` format (for other backends too), see [Hashing](#hashing) for more details about different hashing strategies. Hashes may be generated using the `pw` utility (built by default when running `make`) included in the plugin (or one of your own). Passwords may also be tested using the [pw-test package](https://github.com/iegomez/pw-test).
|
||||
|
@ -516,20 +507,21 @@ The `postgres` backend allows to specify queries for user, superuser and acl ch
|
|||
|
||||
The following `auth_opt_` options are supported:
|
||||
|
||||
| Option | default | Mandatory | Meaning |
|
||||
| -------------- | ----------------- | :---------: | ------------------------ |
|
||||
| pg_host | localhost | | hostname/address
|
||||
| pg_port | 5432 | | TCP port
|
||||
| pg_user | | Y | username
|
||||
| pg_password | | Y | password
|
||||
| pg_dbname | | Y | database name
|
||||
| pg_userquery | | Y | SQL for users
|
||||
| pg_superquery | | N | SQL for superusers
|
||||
| pg_aclquery | | N | SQL for ACLs
|
||||
| pg_sslmode | disable | N | SSL/TLS mode.
|
||||
| pg_sslcert | | N | SSL/TLS Client Cert.
|
||||
| pg_sslkey | | N | SSL/TLS Client Cert. Key
|
||||
| pg_sslrootcert | | N | SSL/TLS Root Cert
|
||||
| Option | default | Mandatory | Meaning |
|
||||
| --------------------- | ----------------- | :---------: | ----------------------------------------------------------- |
|
||||
| pg_host | localhost | | hostname/address |
|
||||
| pg_port | 5432 | | TCP port |
|
||||
| pg_user | | Y | username |
|
||||
| pg_password | | Y | password |
|
||||
| pg_dbname | | Y | database name |
|
||||
| pg_userquery | | Y | SQL for users |
|
||||
| pg_superquery | | N | SQL for superusers |
|
||||
| pg_aclquery | | N | SQL for ACLs |
|
||||
| pg_sslmode | disable | N | SSL/TLS mode. |
|
||||
| pg_sslcert | | N | SSL/TLS Client Cert. |
|
||||
| pg_sslkey | | N | SSL/TLS Client Cert. Key |
|
||||
| pg_sslrootcert | | N | SSL/TLS Root Cert |
|
||||
| pg_connect_tries | -1 | N | x < 0: try forever, x > 0: try x times |
|
||||
|
||||
Depending on the sslmode given, sslcert, sslkey and sslrootcert will be used. Options for sslmode are:
|
||||
|
||||
|
@ -586,12 +578,18 @@ auth_opt_pg_port 5432
|
|||
auth_opt_pg_dbname appserver
|
||||
auth_opt_pg_user appserver
|
||||
auth_opt_pg_password appserver
|
||||
auth_opt_pg_connect_tries 5
|
||||
auth_opt_pg_userquery select password_hash from "user" where username = $1 and is_active = true limit 1
|
||||
auth_opt_pg_superquery select count(*) from "user" where username = $1 and is_admin = true
|
||||
auth_opt_pg_aclquery select distinct 'application/' || a.id || '/#' from "user" u inner join organization_user ou on ou.user_id = u.id inner join organization o on o.id = ou.organization_id inner join application a on a.organization_id = o.id where u.username = $1 and $2 = $2
|
||||
|
||||
```
|
||||
|
||||
**DB connect tries**: on startup, depending on `pg_connect_tries` option, the plugin will try to connect and ping the DB a max number of times or forever every 2 seconds.
|
||||
By default it will try to reconnect forever to maintain backwards compatibility and avoid issues when `mosquitto` starts before the DB service does,
|
||||
but you may choose to ping a max amount of times by setting any positive number.
|
||||
If given 0, the DB will try to connect only once, which would be the same as setting the option to 1.
|
||||
|
||||
#### Password hashing
|
||||
|
||||
For instructions on how to set a backend specific hasher or use the general one, see [Hashing](#hashing).
|
||||
|
@ -652,22 +650,23 @@ auth_opt_mysql_allow_native_passwords true
|
|||
|
||||
Supported options for `mysql` are:
|
||||
|
||||
| Option | default | Mandatory | Meaning |
|
||||
| -------------- | ----------------- | :---------: | ------------------------ |
|
||||
| mysql_host | localhost | N | hostname/address
|
||||
| mysql_port | 3306 | N | TCP port
|
||||
| mysql_user | | Y | username
|
||||
| mysql_password | | Y | password
|
||||
| mysql_dbname | | Y | database name
|
||||
| mysql_userquery | | Y | SQL for users
|
||||
| mysql_superquery | | N | SQL for superusers
|
||||
| mysql_aclquery | | N | SQL for ACLs
|
||||
| mysql_sslmode | disable | N | SSL/TLS mode.
|
||||
| mysql_sslcert | | N | SSL/TLS Client Cert.
|
||||
| mysql_sslkey | | N | SSL/TLS Client Cert. Key
|
||||
| mysql_sslrootcert | | N | SSL/TLS Root Cert
|
||||
| mysql_protocol | tcp | N | Connection protocol
|
||||
| mysql_socket | | N | Unix socket path
|
||||
| Option | default | Mandatory | Meaning |
|
||||
| ------------------------- | ----------------- | :---------: | ----------------------------------------------------------- |
|
||||
| mysql_host | localhost | N | hostname/address |
|
||||
| mysql_port | 3306 | N | TCP port |
|
||||
| mysql_user | | Y | username |
|
||||
| mysql_password | | Y | password |
|
||||
| mysql_dbname | | Y | database name |
|
||||
| mysql_userquery | | Y | SQL for users |
|
||||
| mysql_superquery | | N | SQL for superusers |
|
||||
| mysql_aclquery | | N | SQL for ACLs |
|
||||
| mysql_sslmode | disable | N | SSL/TLS mode. |
|
||||
| mysql_sslcert | | N | SSL/TLS Client Cert. |
|
||||
| mysql_sslkey | | N | SSL/TLS Client Cert. Key |
|
||||
| mysql_sslrootcert | | N | SSL/TLS Root Cert |
|
||||
| mysql_protocol | tcp | N | Connection protocol |
|
||||
| mysql_socket | | N | Unix socket path |
|
||||
| mysql_connect_tries | -1 | N | x < 0: try forever, x > 0: try x times |
|
||||
|
||||
|
||||
Finally, placeholders for mysql differ from those of postgres, changing from $1, $2, etc., to simply ?. These are some **example** queries for `mysql`:
|
||||
|
@ -691,6 +690,11 @@ Acl query:
|
|||
SELECT topic FROM acl WHERE (username = ?) AND rw = ?
|
||||
```
|
||||
|
||||
**DB connect tries**: on startup, depending on `mysql_connect_tries` option, the plugin will try to connect and ping the DB a max number of times or forever every 2 seconds.
|
||||
By default it will try to reconnect forever to maintain backwards compatibility and avoid issues when `mosquitto` starts before the DB service does,
|
||||
but you may choose to ping a max amount of times by setting any positive number.
|
||||
If given 0, the DB will try to connect only once, which would be the same as setting the option to 1.
|
||||
|
||||
#### Password hashing
|
||||
|
||||
For instructions on how to set a backend specific hasher or use the general one, see [Hashing](#hashing).
|
||||
|
@ -737,12 +741,13 @@ ON UPDATE CASCADE
|
|||
The `sqlite` backend works in the same way as `postgres` and `mysql` do, except that being a light weight db, it has fewer configuration options.
|
||||
The following `auth_opt_` options are supported:
|
||||
|
||||
| Option | default | Mandatory | Meaning |
|
||||
| --------------------- | ----------------- | :---------: | ------------------------ |
|
||||
| sqlite_source | | Y | SQLite3 source
|
||||
| sqlite_userquery | | Y | SQL for users
|
||||
| sqlite_superquery | | N | SQL for superusers
|
||||
| sqlite_aclquery | | N | SQL for ACLs
|
||||
| Option | default | Mandatory | Meaning |
|
||||
| ------------------------- | ----------------- | :---------: | ----------------------------------------------------------- |
|
||||
| sqlite_source | | Y | SQLite3 source |
|
||||
| sqlite_userquery | | Y | SQL for users |
|
||||
| sqlite_superquery | | N | SQL for superusers |
|
||||
| sqlite_aclquery | | N | SQL for ACLs |
|
||||
| sqlite_connect_tries | -1 | N | x < 0: try forever, x > 0: try x times |
|
||||
|
||||
SQLite3 allows to connect to an in-memory db, or a single file one, so source maybe `memory` (not :memory:) or the path to a file db.
|
||||
|
||||
|
@ -762,6 +767,11 @@ sqlite_superquery SELECT COUNT(*) FROM account WHERE username = ? AND super = 1
|
|||
sqlite_aclquery SELECT topic FROM acl WHERE (username = ?) AND rw >= ?
|
||||
```
|
||||
|
||||
**DB connect tries**: on startup, depending on `sqlite_connect_tries` option, the plugin will try to connect and ping the DB a max number of times or forever every 2 seconds.
|
||||
By default it will try to reconnect forever to maintain backwards compatibility and avoid issues when `mosquitto` starts before the DB service does,
|
||||
but you may choose to ping a max amount of times by setting any positive number.
|
||||
If given 0, the DB will try to connect only once, which would be the same as setting the option to 1.
|
||||
|
||||
#### Password hashing
|
||||
|
||||
For instructions on how to set a backend specific hasher or use the general one, see [Hashing](#hashing).
|
||||
|
@ -774,11 +784,16 @@ There are no requirements, as the tests create (and later delete) the DB and tab
|
|||
|
||||
### JWT
|
||||
|
||||
The `jwt` backend is for auth with a JWT remote API or a local DB. The option jwt_remote sets the nature of the plugin:
|
||||
The `jwt` backend is for auth with a JWT remote API, a local DB or a JavaScript VM interpreter. Global otions for JWT are:
|
||||
|
||||
```
|
||||
auth_opt_jwt_remote true
|
||||
```
|
||||
| Option | default | Mandatory | Meaning |
|
||||
| ------------------------- | ----------------- | :---------: | ------------------------------------------------------- |
|
||||
| jwt_mode | | Y | local, remote, js |
|
||||
| jwt_parse_token | false | N | Parse token in remote/js modes |
|
||||
| jwt_secret | | Y/N | JWT secret, required for local mode, optional otherwise |
|
||||
| jwt_userfield | | N | When `Username`, expect `username` as part of claims |
|
||||
| jwt_skip_user_expiration | false | N | Skip token expiration in user/superuser checks |
|
||||
| jwt_skip_acl_expiration | false | N | Skip token expiration in ACL checks |
|
||||
|
||||
|
||||
#### Remote mode
|
||||
|
@ -794,8 +809,6 @@ The following `auth_opt_` options are supported by the `jwt` backend when remote
|
|||
| jwt_aclcheck_uri | | Y | URI for check acl |
|
||||
| jwt_with_tls | false | N | Use TLS on connect |
|
||||
| jwt_verify_peer | false | N | Whether to verify peer for tls |
|
||||
| jwt_skip_user_expiration | false | N | Skip token expiration: (super)user check) |
|
||||
| jwt_skip_acl_expiration | false | N | Skip token expiration: acl check |
|
||||
| jwt_response_mode | status | N | Response type (status, json, text) |
|
||||
| jwt_params_mode | json | N | Data type (json, form) |
|
||||
|
||||
|
@ -806,6 +819,11 @@ If the option `jwt_superuser_uri` is not set then `superuser` checks are disable
|
|||
|
||||
For all URIs, the backend will send a request with the `Authorization` header set to `Bearer token`, where token should be a correct JWT token and corresponds to the `username` received from Mosquitto.
|
||||
|
||||
When `jwt_parse_token` is set, the backend will parse the token using `jwt_secret` and extract the username from either the claim's `Subject` (`sub` field), or from the `username` field when `jwt_userfield` is set to `Username`. This `username` will be sent along other params in all requests, and the `Authorization` header will be set to `Bearer token` as usual.
|
||||
|
||||
Notice that failing to provide `jwt_secret` or passing a wrong one will result in an error when parsing the token and the request will not be made.
|
||||
Set these options only if you intend to keep the plugin synced with your JWT service and wish for the former to pre-parse the token.
|
||||
|
||||
##### Response mode
|
||||
|
||||
When response mode is set to `json`, the backend expects the URIs to return a status code (if not 2XX, unauthorized) and a json response, consisting of two fields:
|
||||
|
@ -892,58 +910,35 @@ initMqttClient(applicationID, mode, devEUI) {
|
|||
|
||||
#### Local mode
|
||||
|
||||
*Update: this backend will assume that the username is contained on StandardClaim's Subject field unless told otherwise with the option jwt_userfield. The alternative (which works with `loraserver/chirpstack`) is to set it to Username.*
|
||||
When set to `local` mode, the backend will try to validate JWT tokens against a DB backend, either `postgres` or `mysql`, given by the `jwt_db option`.
|
||||
Options for the DB connection are the almost the same as the ones given in the Postgres and Mysql backends but prefixed with `jwt_`, e.g.:
|
||||
|
||||
```
|
||||
auth_opt_jwt_userfield Username
|
||||
auth_opt_jwt_pg_host localhost
|
||||
```
|
||||
|
||||
When set as remote false, the backend will try to validate JWT tokens against a DB backend, either `postgres` or `mysql`, given by the jwt_db option. Options for the DB connection are the same as the ones given in the Postgres and Mysql backends, but include one new option and 3 options that will override Postgres' or Mysql's ones only for JWT cases (in case both backends are needed). Note that these options will be mandatory (except for jwt_db) only if remote is false.
|
||||
The difference is that a specific `jwt_userquery` returning a count must be given since JWT backend won't use the `password` passed along by `mosquitto`,
|
||||
but instead should only use the `username` derived from the JWT token, e.g.:
|
||||
|
||||
The following `auth_opt_` options are supported:
|
||||
```
|
||||
auth_opt_jwt_userquery select count(*) from test_user where username = $1 limit 1
|
||||
```
|
||||
|
||||
Thus, the following specific JWT local `auth_opt_` options are supported:
|
||||
|
||||
|
||||
| Option | default | Mandatory | Meaning |
|
||||
| -----------------| ----------------- | :---------: | ---------- |
|
||||
| jwt_db | postgres | N | The DB backend to be used |
|
||||
| jwt_secret | | Y | JWT secret to check tokens |
|
||||
| jwt_userquery | | Y | SQL for users |
|
||||
| jwt_superquery | | N | SQL for superusers |
|
||||
| jwt_aclquery | | N | SQL for ACLs |
|
||||
| jwt_userfield | Subject | N | Field to be used for username (Subject or Username) |
|
||||
| Option | default | Mandatory | Meaning |
|
||||
| -----------------| ----------------- | :---------: | -------------------------------------------------------- |
|
||||
| jwt_db | postgres | N | The DB backend to be used, either `postgres` or `mysql` |
|
||||
| jwt_userquery | | Y | SQL query for users |
|
||||
|
||||
|
||||
Also, as it uses the DB backend for local auth, the following DB backend options must be set, though queries (pg_userquery, pg_superquery and pg_aclquery, or mysql_userquery, mysql_superquery and mysql_aclquery) need not to be correct if the backend is not used as they'll be over overridden by the jwt queries when jwt is used for auth:
|
||||
Notice that general `jwt_secret` is mandatory when using this mode.
|
||||
`jwt_userfield` is still optional and serves as a mean to extract the username from either the claim's `Subject` (`sub` field),
|
||||
or from the `username` field when `jwt_userfield` is set to `Username`
|
||||
|
||||
If jwt is used with postgres, these options are needed:
|
||||
|
||||
| Option | default | Mandatory | Meaning |
|
||||
| -------------- | ----------------- | :---------: | ------------------------ |
|
||||
| pg_host | localhost | | hostname/address
|
||||
| pg_port | 5432 | | TCP port
|
||||
| pg_user | | Y | username
|
||||
| pg_password | | Y | password
|
||||
| pg_dbname | | Y | database name
|
||||
| pg_userquery | | Y | SQL for users
|
||||
| pg_superquery | | N | SQL for superusers
|
||||
| pg_aclquery | | N | SQL for ACLs
|
||||
|
||||
|
||||
If, instead, jwt is used with mysql, these options are needed:
|
||||
|
||||
| Option | default | Mandatory | Meaning |
|
||||
| -------------------- | ----------------- | :---------: | ------------------------ |
|
||||
| mysql_host | localhost | | hostname/address
|
||||
| mysql_port | 3306 | | TCP port
|
||||
| mysql_user | | Y | username
|
||||
| mysql_password | | Y | password
|
||||
| mysql_dbname | | Y | database name
|
||||
| mysql_userquery | | Y | SQL for users
|
||||
| mysql_superquery | | N | SQL for superusers
|
||||
| mysql_aclquery | | N | SQL for ACLs
|
||||
|
||||
|
||||
Options for the overridden queries are the same except for the user query, which now expects an integer result instead of a password hash, as the JWT token needs no password checking. An example of a different query using the same DB is given for the user query.
|
||||
As mentioned, only the `userquery` must not be prefixed by the underlying DB, and now expects an integer result instead of a password hash, as the JWT token needs no password checking.
|
||||
An example of a different query using either DB is given for the user query.
|
||||
|
||||
For postgres:
|
||||
|
||||
|
@ -957,16 +952,66 @@ For mysql:
|
|||
auth_opt_jwt_userquery select count(*) from "user" where username = ? and is_active = true limit 1
|
||||
```
|
||||
|
||||
|
||||
*Important note:*
|
||||
|
||||
When option jwt_superquery is not present, Superuser check will always return false, hence there'll be no superusers.
|
||||
Since local JWT follows the underlying DB backend's way of working, both of these hold true:
|
||||
|
||||
When option jwt_aclquery is not present, AclCheck will always return true, hence all authenticated users will be authorized to pub/sub to any topic.
|
||||
- When option jwt_superquery is not present, Superuser check will always return false, hence there'll be no superusers.
|
||||
- When option jwt_aclquery is not present, AclCheck will always return true, hence all authenticated users will be authorized to pub/sub to any topic.
|
||||
|
||||
|
||||
#### JS mode
|
||||
|
||||
The last mode for this backend is JS mode, which allows to run a JavaScript interpreter VM to conduct checks. Options for this mode are:
|
||||
|
||||
| Option | default | Mandatory | Meaning |
|
||||
| ------------------------------| --------------- | :---------: | ----------------------------------------------------- |
|
||||
| jwt_js_stack_depth_limit | 32 | N | Max stack depth for the interpreter |
|
||||
| jwt_js_ms_max_duration | 200 | N | Max execution time for a hceck in milliseconds |
|
||||
| jwt_js_user_script_path | | Y | Relative or absolute path to user check script |
|
||||
| jwt_js_superuser_script_path | | Y | Relative or absolute path to superuser check script |
|
||||
| jwt_js_acl_script_path | | Y | Relative or absolute path to ACL check script |
|
||||
|
||||
This mode expects the user to define JS scripts that return a boolean result to the check in question.
|
||||
|
||||
The backend will pass `mosquitto` provided arguments along, that is `token` for both `user` and `superuser` check; `token`, `topic`, `clientid` and `acc` for `ACL` checks.
|
||||
|
||||
Optionally, `username` will be passed as an argument when `auth_opt_jwt_parse_token` option is set. As with remote mode, this will need `auth_opt_jwt_secret` to be set and correct,
|
||||
and `auth_opt_jwt_userfield` to be optionally set.
|
||||
|
||||
This is a valid, albeit pretty useless, example script for ACL checks (see `test-files/jwt` dir for test scripts):
|
||||
|
||||
```
|
||||
function checkAcl(token, topic, clientid, acc) {
|
||||
if(token != "correct") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(topic != "test/topic") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(clientid != "id") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(acc != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
checkAcl(token, topic, clientid, acc);
|
||||
```
|
||||
|
||||
With `auth_opt_jwt_parse_token` the signature would be `function checkAcl(token, topic, clientid, acc, username)` instead.
|
||||
|
||||
Finally, this mode uses [otto](https://github.com/robertkrimen/otto) under the hood to run the scripts. Please check their documentation for supported features and known limitations.
|
||||
|
||||
#### Password hashing
|
||||
|
||||
When using local mode, a hasher is expected. For instructions on how to set a backend specific hasher or use the general one, see [Hashing](#hashing).
|
||||
Since JWT needs not to check passwords, there's no need to configure a `hasher`.
|
||||
|
||||
#### Prefixes
|
||||
|
||||
|
@ -1232,7 +1277,7 @@ As this option is custom written by yourself, there are no tests included in the
|
|||
|
||||
### gRPC
|
||||
|
||||
The `grpc` allows to check for user auth, superuser and acls against a gRPC service.
|
||||
The `grpc` backend allows to check for user auth, superuser and acls against a gRPC service.
|
||||
|
||||
The following `auth_opt_` options are supported:
|
||||
|
||||
|
@ -1318,6 +1363,61 @@ message NameResponse {
|
|||
|
||||
This backend has no special requirements as a gRPC server is mocked to test different scenarios.
|
||||
|
||||
### Javascript
|
||||
|
||||
The `javascript` backend allows to run a JavaScript interpreter VM to conduct checks. Options for this mode are:
|
||||
|
||||
| Option | default | Mandatory | Meaning |
|
||||
| --------------------------| --------------- | :---------: | ----------------------------------------------------- |
|
||||
| js_stack_depth_limit | 32 | N | Max stack depth for the interpreter |
|
||||
| js_ms_max_duration | 200 | N | Max execution time for a hceck in milliseconds |
|
||||
| js_user_script_path | | Y | Relative or absolute path to user check script |
|
||||
| js_superuser_script_path | | Y | Relative or absolute path to superuser check script |
|
||||
| js_acl_script_path | | Y | Relative or absolute path to ACL check script |
|
||||
|
||||
This backend expects the user to define JS scripts that return a boolean result to the check in question.
|
||||
|
||||
The backend will pass `mosquitto` provided arguments along, that is:
|
||||
- `username`, `password` and `clientid` for `user` checks.
|
||||
- `username` for `superuser` checks.
|
||||
- `username`, `topic`, `clientid` and `acc` for `ACL` checks.
|
||||
|
||||
|
||||
This is a valid, albeit pretty useless, example script for ACL checks (see `test-files/jwt` dir for test scripts):
|
||||
|
||||
```
|
||||
function checkAcl(username, topic, clientid, acc) {
|
||||
if(username != "correct") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(topic != "test/topic") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(clientid != "id") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(acc != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
checkAcl(username, topic, clientid, acc);
|
||||
```
|
||||
|
||||
#### Password hashing
|
||||
|
||||
Notice the `password` will be passed to the script as given by `mosquitto`, leaving any hashing to the script.
|
||||
|
||||
|
||||
#### Testing Javascript
|
||||
|
||||
This backend has no special requirements as `javascript` test files are provided to test different scenarios.
|
||||
|
||||
|
||||
### Using with LoRa Server
|
||||
|
||||
|
|
|
@ -3,15 +3,11 @@
|
|||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <mosquitto.h>
|
||||
#include <mosquitto_broker.h>
|
||||
#include <mosquitto_plugin.h>
|
||||
#include <mosquitto.h>
|
||||
|
||||
|
||||
|
||||
#if MOSQ_AUTH_PLUGIN_VERSION >= 3
|
||||
# include <mosquitto_broker.h>
|
||||
#endif
|
||||
|
||||
#if MOSQ_AUTH_PLUGIN_VERSION >= 3
|
||||
# define mosquitto_auth_opt mosquitto_opt
|
||||
#endif
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package backends
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
@ -11,20 +12,33 @@ import (
|
|||
// OpenDatabase opens the database and performs a ping to make sure the
|
||||
// database is up.
|
||||
// Taken from brocaar's lora-app-server: https://github.com/brocaar/lora-app-server
|
||||
func OpenDatabase(dsn, engine string) (*sqlx.DB, error) {
|
||||
func OpenDatabase(dsn, engine string, tries int) (*sqlx.DB, error) {
|
||||
|
||||
db, err := sqlx.Open(engine, dsn)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "database connection error")
|
||||
}
|
||||
|
||||
for {
|
||||
if tries == 0 {
|
||||
tries = 1
|
||||
}
|
||||
|
||||
for tries != 0 {
|
||||
if err = db.Ping(); err != nil {
|
||||
log.Errorf("ping database error, will retry in 2s: %s", err)
|
||||
log.Errorf("ping database %s error, will retry in 2s: %s", engine, err)
|
||||
time.Sleep(2 * time.Second)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
if tries > 0 {
|
||||
tries--
|
||||
}
|
||||
}
|
||||
|
||||
// Return last ping error when done trying.
|
||||
if tries == 0 {
|
||||
return nil, fmt.Errorf("couldn't ping database %s: %s", engine, err)
|
||||
}
|
||||
|
||||
return db, nil
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
package backends
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/iegomez/mosquitto-go-auth/backends/js"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Javascript struct {
|
||||
stackDepthLimit int
|
||||
msMaxDuration int64
|
||||
|
||||
userScript string
|
||||
superuserScript string
|
||||
aclScript string
|
||||
|
||||
runner *js.Runner
|
||||
}
|
||||
|
||||
func NewJavascript(authOpts map[string]string, logLevel log.Level) (*Javascript, error) {
|
||||
|
||||
log.SetLevel(logLevel)
|
||||
|
||||
javascript := &Javascript{
|
||||
stackDepthLimit: js.DefaultStackDepthLimit,
|
||||
msMaxDuration: js.DefaultMsMaxDuration,
|
||||
}
|
||||
|
||||
jsOk := true
|
||||
missingOptions := ""
|
||||
|
||||
if stackLimit, ok := authOpts["js_stack_depth_limit"]; ok {
|
||||
limit, err := strconv.ParseInt(stackLimit, 10, 64)
|
||||
if err != nil {
|
||||
log.Errorf("invalid stack depth limit %s, defaulting to %d", stackLimit, js.DefaultStackDepthLimit)
|
||||
} else {
|
||||
javascript.stackDepthLimit = int(limit)
|
||||
}
|
||||
}
|
||||
|
||||
if maxDuration, ok := authOpts["js_ms_max_duration"]; ok {
|
||||
duration, err := strconv.ParseInt(maxDuration, 10, 64)
|
||||
if err != nil {
|
||||
log.Errorf("invalid stack depth limit %s, defaulting to %d", maxDuration, js.DefaultMsMaxDuration)
|
||||
} else {
|
||||
javascript.msMaxDuration = duration
|
||||
}
|
||||
}
|
||||
|
||||
if userScriptPath, ok := authOpts["js_user_script_path"]; ok {
|
||||
script, err := js.LoadScript(userScriptPath)
|
||||
if err != nil {
|
||||
return javascript, err
|
||||
}
|
||||
|
||||
javascript.userScript = script
|
||||
} else {
|
||||
jsOk = false
|
||||
missingOptions += " js_user_script_path"
|
||||
}
|
||||
|
||||
if superuserScriptPath, ok := authOpts["js_superuser_script_path"]; ok {
|
||||
script, err := js.LoadScript(superuserScriptPath)
|
||||
if err != nil {
|
||||
return javascript, err
|
||||
}
|
||||
|
||||
javascript.superuserScript = script
|
||||
} else {
|
||||
jsOk = false
|
||||
missingOptions += " js_superuser_script_path"
|
||||
}
|
||||
|
||||
if aclScriptPath, ok := authOpts["js_acl_script_path"]; ok {
|
||||
script, err := js.LoadScript(aclScriptPath)
|
||||
if err != nil {
|
||||
return javascript, err
|
||||
}
|
||||
|
||||
javascript.aclScript = script
|
||||
} else {
|
||||
jsOk = false
|
||||
missingOptions += " js_acl_script_path"
|
||||
}
|
||||
|
||||
//Exit if any mandatory option is missing.
|
||||
if !jsOk {
|
||||
return nil, errors.Errorf("Javascript backend error: missing options: %s", missingOptions)
|
||||
}
|
||||
|
||||
javascript.runner = js.NewRunner(javascript.stackDepthLimit, javascript.msMaxDuration)
|
||||
|
||||
return javascript, nil
|
||||
}
|
||||
|
||||
func (o *Javascript) GetUser(username, password, clientid string) (bool, error) {
|
||||
params := map[string]interface{}{
|
||||
"username": username,
|
||||
"password": password,
|
||||
"clientid": clientid,
|
||||
}
|
||||
|
||||
granted, err := o.runner.RunScript(o.userScript, params)
|
||||
if err != nil {
|
||||
log.Errorf("js error: %s", err)
|
||||
}
|
||||
|
||||
return granted, err
|
||||
}
|
||||
|
||||
func (o *Javascript) GetSuperuser(username string) (bool, error) {
|
||||
params := map[string]interface{}{
|
||||
"username": username,
|
||||
}
|
||||
|
||||
granted, err := o.runner.RunScript(o.superuserScript, params)
|
||||
if err != nil {
|
||||
log.Errorf("js error: %s", err)
|
||||
}
|
||||
|
||||
return granted, err
|
||||
}
|
||||
|
||||
func (o *Javascript) CheckAcl(username, topic, clientid string, acc int32) (bool, error) {
|
||||
params := map[string]interface{}{
|
||||
"username": username,
|
||||
"topic": topic,
|
||||
"clientid": clientid,
|
||||
"acc": acc,
|
||||
}
|
||||
|
||||
granted, err := o.runner.RunScript(o.aclScript, params)
|
||||
if err != nil {
|
||||
log.Errorf("js error: %s", err)
|
||||
}
|
||||
|
||||
return granted, err
|
||||
}
|
||||
|
||||
//GetName returns the backend's name
|
||||
func (o *Javascript) GetName() string {
|
||||
return "Javascript"
|
||||
}
|
||||
|
||||
func (o *Javascript) Halt() {
|
||||
// NO-OP
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package backends
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestJavascript(t *testing.T) {
|
||||
authOpts := make(map[string]string)
|
||||
|
||||
authOpts["js_user_script_path"] = "../test-files/js/user_script.js"
|
||||
authOpts["js_superuser_script_path"] = "../test-files/js/superuser_script.js"
|
||||
authOpts["js_acl_script_path"] = "../test-files/js/acl_script.js"
|
||||
|
||||
Convey("When constructing a Javascript backend", t, func() {
|
||||
Convey("It returns error if there's a missing option", func() {
|
||||
badOpts := make(map[string]string)
|
||||
|
||||
badOpts["js_user_script"] = authOpts["js_user_script"]
|
||||
badOpts["js_superuser_script"] = authOpts["js_superuser_script"]
|
||||
|
||||
_, err := NewJavascript(badOpts, log.DebugLevel)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("It returns error if a script can't be opened", func() {
|
||||
badOpts := make(map[string]string)
|
||||
|
||||
badOpts["js_user_script"] = authOpts["js_user_script"]
|
||||
badOpts["js_superuser_script"] = authOpts["js_superuser_script"]
|
||||
badOpts["js_acl_script_path"] = "../test-files/js/nothing_here.js"
|
||||
|
||||
_, err := NewJavascript(badOpts, log.DebugLevel)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
javascript, err := NewJavascript(authOpts, log.DebugLevel)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("User checks should work", func() {
|
||||
userResponse, err := javascript.GetUser("correct", "good", "some-id")
|
||||
So(err, ShouldBeNil)
|
||||
So(userResponse, ShouldBeTrue)
|
||||
|
||||
userResponse, err = javascript.GetUser("correct", "bad", "some-id")
|
||||
So(err, ShouldBeNil)
|
||||
So(userResponse, ShouldBeFalse)
|
||||
|
||||
userResponse, err = javascript.GetUser("wrong", "good", "some-id")
|
||||
So(err, ShouldBeNil)
|
||||
So(userResponse, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("Superuser checks should work", func() {
|
||||
superuserResponse, err := javascript.GetSuperuser("admin")
|
||||
So(err, ShouldBeNil)
|
||||
So(superuserResponse, ShouldBeTrue)
|
||||
|
||||
superuserResponse, err = javascript.GetSuperuser("non-admin")
|
||||
So(err, ShouldBeNil)
|
||||
So(superuserResponse, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("ACL checks should work", func() {
|
||||
aclResponse, err := javascript.CheckAcl("correct", "test/topic", "id", 1)
|
||||
So(err, ShouldBeNil)
|
||||
So(aclResponse, ShouldBeTrue)
|
||||
|
||||
aclResponse, err = javascript.CheckAcl("incorrect", "test/topic", "id", 1)
|
||||
So(err, ShouldBeNil)
|
||||
So(aclResponse, ShouldBeFalse)
|
||||
|
||||
aclResponse, err = javascript.CheckAcl("correct", "bad/topic", "id", 1)
|
||||
So(err, ShouldBeNil)
|
||||
So(aclResponse, ShouldBeFalse)
|
||||
|
||||
aclResponse, err = javascript.CheckAcl("correct", "test/topic", "wrong-id", 1)
|
||||
So(err, ShouldBeNil)
|
||||
So(aclResponse, ShouldBeFalse)
|
||||
|
||||
aclResponse, err = javascript.CheckAcl("correct", "test/topic", "id", 2)
|
||||
So(err, ShouldBeNil)
|
||||
So(aclResponse, ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package js
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
// Default conf values for runner.
|
||||
const (
|
||||
DefaultStackDepthLimit = 32
|
||||
DefaultMsMaxDuration = 200
|
||||
)
|
||||
|
||||
type Runner struct {
|
||||
StackDepthLimit int
|
||||
MsMaxDuration int64
|
||||
}
|
||||
|
||||
var Halt = errors.New("exceeded max execution time")
|
||||
|
||||
func NewRunner(stackDepthLimit int, msMaxDuration int64) *Runner {
|
||||
return &Runner{
|
||||
StackDepthLimit: stackDepthLimit,
|
||||
MsMaxDuration: msMaxDuration,
|
||||
}
|
||||
}
|
||||
|
||||
func LoadScript(path string) (string, error) {
|
||||
script, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(script), nil
|
||||
}
|
||||
|
||||
func (o *Runner) RunScript(script string, params map[string]interface{}) (granted bool, err error) {
|
||||
// The VM is not thread-safe, so we need to create a new VM on every run.
|
||||
// TODO: This could be enhanced by having a pool of VMs.
|
||||
vm := otto.New()
|
||||
vm.SetStackDepthLimit(o.StackDepthLimit)
|
||||
vm.Interrupt = make(chan func(), 1)
|
||||
|
||||
defer func() {
|
||||
if caught := recover(); caught != nil {
|
||||
if caught == Halt {
|
||||
granted = false
|
||||
err = Halt
|
||||
return
|
||||
}
|
||||
panic(caught)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
time.Sleep(time.Duration(o.MsMaxDuration) * time.Millisecond)
|
||||
vm.Interrupt <- func() {
|
||||
panic(Halt)
|
||||
}
|
||||
}()
|
||||
|
||||
for k, v := range params {
|
||||
vm.Set(k, v)
|
||||
}
|
||||
|
||||
val, err := vm.Run(script)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
granted, err = val.ToBoolean()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
541
backends/jwt.go
541
backends/jwt.go
|
@ -1,489 +1,130 @@
|
|||
package backends
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
h "net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
jwtGo "github.com/dgrijalva/jwt-go"
|
||||
"github.com/iegomez/mosquitto-go-auth/hashing"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type JWT struct {
|
||||
Remote bool
|
||||
LocalDB string
|
||||
|
||||
Postgres Postgres
|
||||
Mysql Mysql
|
||||
Secret string
|
||||
UserQuery string
|
||||
SuperuserQuery string
|
||||
AclQuery string
|
||||
|
||||
UserUri string
|
||||
SuperuserUri string
|
||||
AclUri string
|
||||
Host string
|
||||
Port string
|
||||
WithTLS bool
|
||||
VerifyPeer bool
|
||||
SkipUserExpiration bool
|
||||
SkipACLExpiration bool
|
||||
|
||||
ParamsMode string
|
||||
ResponseMode string
|
||||
|
||||
UserField string
|
||||
|
||||
Client *h.Client
|
||||
|
||||
hasher hashing.HashComparer
|
||||
mode string
|
||||
checker jwtChecker
|
||||
}
|
||||
|
||||
// Claims defines the struct containing the token claims. StandardClaim's Subject field should contain the username, unless an opt is set to support Username field.
|
||||
type tokenOptions struct {
|
||||
parseToken bool
|
||||
skipUserExpiration bool
|
||||
skipACLExpiration bool
|
||||
secret string
|
||||
userField string
|
||||
}
|
||||
|
||||
type jwtChecker interface {
|
||||
GetUser(username string) (bool, error)
|
||||
GetSuperuser(username string) (bool, error)
|
||||
CheckAcl(username, topic, clientid string, acc int32) (bool, error)
|
||||
Halt()
|
||||
}
|
||||
|
||||
// Claims defines the struct containing the token claims.
|
||||
// StandardClaim's Subject field should contain the username, unless an opt is set to support Username field.
|
||||
type Claims struct {
|
||||
jwt.StandardClaims
|
||||
jwtGo.StandardClaims
|
||||
// If set, Username defines the identity of the user.
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Ok bool `json:"ok"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func NewJWT(authOpts map[string]string, logLevel log.Level, hasher hashing.HashComparer) (JWT, error) {
|
||||
const (
|
||||
remoteMode = "remote"
|
||||
localMode = "local"
|
||||
jsMode = "js"
|
||||
)
|
||||
|
||||
func NewJWT(authOpts map[string]string, logLevel log.Level, hasher hashing.HashComparer) (*JWT, error) {
|
||||
log.SetLevel(logLevel)
|
||||
|
||||
//Initialize with defaults
|
||||
var jwt = JWT{
|
||||
Remote: false,
|
||||
WithTLS: false,
|
||||
VerifyPeer: false,
|
||||
ResponseMode: "status",
|
||||
ParamsMode: "json",
|
||||
LocalDB: "postgres",
|
||||
UserField: "Subject",
|
||||
hasher: hasher,
|
||||
}
|
||||
jwt := &JWT{}
|
||||
|
||||
if userField, ok := authOpts["jwt_userfield"]; ok && userField == "Username" {
|
||||
jwt.UserField = userField
|
||||
} else {
|
||||
log.Debugln("JWT user field not present or incorrect, defaulting to Subject field.")
|
||||
}
|
||||
var err error
|
||||
var checker jwtChecker
|
||||
|
||||
if remote, ok := authOpts["jwt_remote"]; ok && remote == "true" {
|
||||
jwt.Remote = true
|
||||
var options tokenOptions
|
||||
|
||||
if parseToken, ok := authOpts["jwt_parse_token"]; ok && parseToken == "true" {
|
||||
options.parseToken = true
|
||||
}
|
||||
|
||||
if skipUserExpiration, ok := authOpts["jwt_skip_user_expiration"]; ok && skipUserExpiration == "true" {
|
||||
jwt.SkipUserExpiration = true
|
||||
options.skipUserExpiration = true
|
||||
}
|
||||
|
||||
if skipACLExpiration, ok := authOpts["jwt_skip_acl_expiration"]; ok && skipACLExpiration == "true" {
|
||||
jwt.SkipACLExpiration = true
|
||||
options.skipACLExpiration = true
|
||||
}
|
||||
|
||||
//If remote, set remote api fields. Else, set jwt secret.
|
||||
if jwt.Remote {
|
||||
|
||||
missingOpts := ""
|
||||
remoteOk := true
|
||||
|
||||
if responseMode, ok := authOpts["jwt_response_mode"]; ok {
|
||||
if responseMode == "text" || responseMode == "json" {
|
||||
jwt.ResponseMode = responseMode
|
||||
}
|
||||
}
|
||||
|
||||
if paramsMode, ok := authOpts["jwt_params_mode"]; ok {
|
||||
if paramsMode == "form" {
|
||||
jwt.ParamsMode = paramsMode
|
||||
}
|
||||
}
|
||||
|
||||
if userUri, ok := authOpts["jwt_getuser_uri"]; ok {
|
||||
jwt.UserUri = userUri
|
||||
} else {
|
||||
remoteOk = false
|
||||
missingOpts += " jwt_getuser_uri"
|
||||
}
|
||||
|
||||
if superuserUri, ok := authOpts["jwt_superuser_uri"]; ok {
|
||||
jwt.SuperuserUri = superuserUri
|
||||
}
|
||||
|
||||
if aclUri, ok := authOpts["jwt_aclcheck_uri"]; ok {
|
||||
jwt.AclUri = aclUri
|
||||
} else {
|
||||
remoteOk = false
|
||||
missingOpts += " jwt_aclcheck_uri"
|
||||
}
|
||||
|
||||
if hostname, ok := authOpts["jwt_host"]; ok {
|
||||
jwt.Host = hostname
|
||||
} else {
|
||||
remoteOk = false
|
||||
missingOpts += " jwt_host"
|
||||
}
|
||||
|
||||
if port, ok := authOpts["jwt_port"]; ok {
|
||||
jwt.Port = port
|
||||
} else {
|
||||
remoteOk = false
|
||||
missingOpts += " jwt_port"
|
||||
}
|
||||
|
||||
if withTLS, ok := authOpts["jwt_with_tls"]; ok && withTLS == "true" {
|
||||
jwt.WithTLS = true
|
||||
}
|
||||
|
||||
if verifyPeer, ok := authOpts["jwt_verify_peer"]; ok && verifyPeer == "true" {
|
||||
jwt.VerifyPeer = true
|
||||
}
|
||||
|
||||
if !remoteOk {
|
||||
return jwt, errors.Errorf("JWT backend error: missing remote options: %s", missingOpts)
|
||||
}
|
||||
|
||||
jwt.Client = &h.Client{Timeout: 5 * time.Second}
|
||||
|
||||
if !jwt.VerifyPeer {
|
||||
tr := &h.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
jwt.Client.Transport = tr
|
||||
}
|
||||
if secret, ok := authOpts["jwt_secret"]; ok {
|
||||
options.secret = secret
|
||||
}
|
||||
|
||||
if userField, ok := authOpts["jwt_userfield"]; ok && userField == "Username" {
|
||||
options.userField = userField
|
||||
} else {
|
||||
|
||||
missingOpts := ""
|
||||
localOk := true
|
||||
|
||||
if secret, ok := authOpts["jwt_secret"]; ok {
|
||||
jwt.Secret = secret
|
||||
} else {
|
||||
return jwt, errors.New("JWT backend error: missing jwt secret")
|
||||
}
|
||||
|
||||
if userQuery, ok := authOpts["jwt_userquery"]; ok {
|
||||
jwt.UserQuery = userQuery
|
||||
} else {
|
||||
localOk = false
|
||||
missingOpts += " jwt_userquery"
|
||||
}
|
||||
|
||||
if superuserQuery, ok := authOpts["jwt_superquery"]; ok {
|
||||
jwt.SuperuserQuery = superuserQuery
|
||||
}
|
||||
|
||||
if aclQuery, ok := authOpts["jwt_aclquery"]; ok {
|
||||
jwt.AclQuery = aclQuery
|
||||
}
|
||||
|
||||
if localDB, ok := authOpts["jwt_db"]; ok {
|
||||
jwt.LocalDB = localDB
|
||||
}
|
||||
|
||||
if !localOk {
|
||||
return jwt, errors.Errorf("JWT backend error: missing local options: %s", missingOpts)
|
||||
}
|
||||
|
||||
if jwt.LocalDB == "mysql" {
|
||||
//Try to create a mysql backend with these custom queries
|
||||
mysql, err := NewMysql(authOpts, logLevel, hasher)
|
||||
if err != nil {
|
||||
return jwt, errors.Errorf("JWT backend error: couldn't create mysql connector for local jwt: %s", err)
|
||||
}
|
||||
mysql.UserQuery = jwt.UserQuery
|
||||
mysql.SuperuserQuery = jwt.SuperuserQuery
|
||||
mysql.AclQuery = jwt.AclQuery
|
||||
|
||||
jwt.Mysql = mysql
|
||||
} else {
|
||||
//Try to create a postgres backend with these custom queries.
|
||||
postgres, err := NewPostgres(authOpts, logLevel, hasher)
|
||||
if err != nil {
|
||||
return jwt, errors.Errorf("JWT backend error: couldn't create postgres connector for local jwt: %s", err)
|
||||
}
|
||||
postgres.UserQuery = jwt.UserQuery
|
||||
postgres.SuperuserQuery = jwt.SuperuserQuery
|
||||
postgres.AclQuery = jwt.AclQuery
|
||||
|
||||
jwt.Postgres = postgres
|
||||
}
|
||||
|
||||
options.userField = "Subject"
|
||||
}
|
||||
|
||||
switch authOpts["jwt_mode"] {
|
||||
case jsMode:
|
||||
jwt.mode = jsMode
|
||||
checker, err = NewJsJWTChecker(authOpts, options)
|
||||
case localMode:
|
||||
jwt.mode = localMode
|
||||
checker, err = NewLocalJWTChecker(authOpts, logLevel, hasher, options)
|
||||
case remoteMode:
|
||||
jwt.mode = remoteMode
|
||||
checker, err = NewRemoteJWTChecker(authOpts, options)
|
||||
default:
|
||||
err = errors.New("unknown JWT mode")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
jwt.checker = checker
|
||||
|
||||
return jwt, nil
|
||||
}
|
||||
|
||||
//GetUser authenticates a given user.
|
||||
func (o JWT) GetUser(token, password, clientid string) (bool, error) {
|
||||
|
||||
if o.Remote {
|
||||
var dataMap map[string]interface{}
|
||||
var urlValues = url.Values{}
|
||||
return o.jwtRequest(o.Host, o.UserUri, token, o.WithTLS, o.VerifyPeer, dataMap, o.Port, o.ParamsMode, o.ResponseMode, urlValues)
|
||||
}
|
||||
|
||||
//If not remote, get the claims and check against postgres for user.
|
||||
claims, err := o.getClaims(token, o.SkipUserExpiration)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("jwt get user error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
//Now check against the db.
|
||||
if o.UserField == "Username" {
|
||||
return o.getLocalUser(claims.Username)
|
||||
}
|
||||
return o.getLocalUser(claims.Subject)
|
||||
|
||||
func (o *JWT) GetUser(token, password, clientid string) (bool, error) {
|
||||
return o.checker.GetUser(token)
|
||||
}
|
||||
|
||||
//GetSuperuser checks if the given user is a superuser.
|
||||
func (o JWT) GetSuperuser(token string) (bool, error) {
|
||||
if o.Remote {
|
||||
if o.SuperuserUri == "" {
|
||||
return false, nil
|
||||
}
|
||||
var dataMap map[string]interface{}
|
||||
var urlValues = url.Values{}
|
||||
return o.jwtRequest(o.Host, o.SuperuserUri, token, o.WithTLS, o.VerifyPeer, dataMap, o.Port, o.ParamsMode, o.ResponseMode, urlValues)
|
||||
}
|
||||
|
||||
//If not remote, get the claims and check against postgres for user.
|
||||
//But check first that there's superuser query.
|
||||
if o.SuperuserQuery == "" {
|
||||
return false, nil
|
||||
}
|
||||
claims, err := o.getClaims(token, o.SkipUserExpiration)
|
||||
|
||||
if err != nil {
|
||||
log.Debugf("jwt get superuser error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
//Now check against db
|
||||
if o.UserField == "Username" {
|
||||
if o.LocalDB == "mysql" {
|
||||
return o.Mysql.GetSuperuser(claims.Username)
|
||||
} else {
|
||||
return o.Postgres.GetSuperuser(claims.Username)
|
||||
}
|
||||
}
|
||||
|
||||
if o.LocalDB == "mysql" {
|
||||
return o.Mysql.GetSuperuser(claims.Subject)
|
||||
} else {
|
||||
return o.Postgres.GetSuperuser(claims.Subject)
|
||||
}
|
||||
|
||||
func (o *JWT) GetSuperuser(token string) (bool, error) {
|
||||
return o.checker.GetSuperuser(token)
|
||||
}
|
||||
|
||||
//CheckAcl checks user authorization.
|
||||
func (o JWT) CheckAcl(token, topic, clientid string, acc int32) (bool, error) {
|
||||
|
||||
if o.Remote {
|
||||
dataMap := map[string]interface{}{
|
||||
"clientid": clientid,
|
||||
"topic": topic,
|
||||
"acc": acc,
|
||||
}
|
||||
var urlValues = url.Values{
|
||||
"clientid": []string{clientid},
|
||||
"topic": []string{topic},
|
||||
"acc": []string{strconv.Itoa(int(acc))},
|
||||
}
|
||||
return o.jwtRequest(o.Host, o.AclUri, token, o.WithTLS, o.VerifyPeer, dataMap, o.Port, o.ParamsMode, o.ResponseMode, urlValues)
|
||||
}
|
||||
|
||||
//If not remote, get the claims and check against postgres for user.
|
||||
//But check first that there's acl query.
|
||||
if o.AclQuery == "" {
|
||||
return true, nil
|
||||
}
|
||||
claims, err := o.getClaims(token, o.SkipACLExpiration)
|
||||
|
||||
if err != nil {
|
||||
log.Debugf("jwt check acl error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
//Now check against the db.
|
||||
if o.UserField == "Username" {
|
||||
if o.LocalDB == "mysql" {
|
||||
return o.Mysql.CheckAcl(claims.Username, topic, clientid, acc)
|
||||
} else {
|
||||
return o.Postgres.CheckAcl(claims.Username, topic, clientid, acc)
|
||||
}
|
||||
}
|
||||
if o.LocalDB == "mysql" {
|
||||
return o.Mysql.CheckAcl(claims.Subject, topic, clientid, acc)
|
||||
} else {
|
||||
return o.Postgres.CheckAcl(claims.Subject, topic, clientid, acc)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (o JWT) jwtRequest(host, uri, token string, withTLS, verifyPeer bool, dataMap map[string]interface{}, port, paramsMode, responseMode string, urlValues url.Values) (bool, error) {
|
||||
|
||||
// Don't do the request if the client is nil.
|
||||
if o.Client == nil {
|
||||
return false, errors.New("client not initilized")
|
||||
}
|
||||
|
||||
tlsStr := "http://"
|
||||
|
||||
if withTLS {
|
||||
tlsStr = "https://"
|
||||
}
|
||||
|
||||
fullUri := fmt.Sprintf("%s%s%s", tlsStr, host, uri)
|
||||
if port != "" {
|
||||
fullUri = fmt.Sprintf("%s%s:%s%s", tlsStr, host, port, uri)
|
||||
}
|
||||
|
||||
var resp *http.Response
|
||||
var err error
|
||||
var req *http.Request
|
||||
|
||||
if paramsMode == "json" {
|
||||
dataJson, err := json.Marshal(dataMap)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("marshal error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
contentReader := bytes.NewReader(dataJson)
|
||||
req, err = http.NewRequest("POST", fullUri, contentReader)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("req error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
} else {
|
||||
req, err = http.NewRequest("POST", fullUri, strings.NewReader(urlValues.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Content-Length", strconv.Itoa(len(urlValues.Encode())))
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("req error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
|
||||
resp, err = o.Client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("error: %v", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("read error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
log.Infof("error code: %d", resp.StatusCode)
|
||||
if resp.StatusCode >= 500 {
|
||||
err = fmt.Errorf("error code: %d", resp.StatusCode)
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
if responseMode == "text" {
|
||||
|
||||
//For test response, we expect "ok" or an error message.
|
||||
if string(body) != "ok" {
|
||||
log.Infof("api error: %s", string(body))
|
||||
return false, nil
|
||||
}
|
||||
|
||||
} else if responseMode == "json" {
|
||||
|
||||
//For json response, we expect Ok and Error fields.
|
||||
response := Response{Ok: false, Error: ""}
|
||||
err = json.Unmarshal(body, &response)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("unmarshal error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !response.Ok {
|
||||
log.Infof("api error: %s", response.Error)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
log.Debugf("jwt request approved for %s", token)
|
||||
return true, nil
|
||||
|
||||
func (o *JWT) CheckAcl(token, topic, clientid string, acc int32) (bool, error) {
|
||||
return o.checker.CheckAcl(token, topic, clientid, acc)
|
||||
}
|
||||
|
||||
//GetName returns the backend's name
|
||||
func (o JWT) GetName() string {
|
||||
func (o *JWT) GetName() string {
|
||||
return "JWT"
|
||||
}
|
||||
|
||||
func (o JWT) getLocalUser(username string) (bool, error) {
|
||||
//If there's no user query, return false.
|
||||
if o.UserQuery == "" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var count sql.NullInt64
|
||||
var err error
|
||||
if o.LocalDB == "mysql" {
|
||||
err = o.Mysql.DB.Get(&count, o.UserQuery, username)
|
||||
} else {
|
||||
err = o.Postgres.DB.Get(&count, o.UserQuery, username)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Debugf("local JWT get user error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !count.Valid {
|
||||
log.Debugf("local JWT get user error: user %s not found", username)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if count.Int64 > 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
//Halt closes any db connection.
|
||||
func (o *JWT) Halt() {
|
||||
o.checker.Halt()
|
||||
}
|
||||
|
||||
func (o JWT) getClaims(tokenStr string, skipExpiration bool) (*Claims, error) {
|
||||
func getJWTClaims(secret string, tokenStr string, skipExpiration bool) (*Claims, error) {
|
||||
|
||||
jwtToken, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(o.Secret), nil
|
||||
jwtToken, err := jwtGo.ParseWithClaims(tokenStr, &Claims{}, func(token *jwtGo.Token) (interface{}, error) {
|
||||
return []byte(secret), nil
|
||||
})
|
||||
|
||||
expirationError := false
|
||||
|
@ -493,7 +134,7 @@ func (o JWT) getClaims(tokenStr string, skipExpiration bool) (*Claims, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if v, ok := err.(*jwt.ValidationError); ok && v.Errors == jwt.ValidationErrorExpired {
|
||||
if v, ok := err.(*jwtGo.ValidationError); ok && v.Errors == jwtGo.ValidationErrorExpired {
|
||||
expirationError = true
|
||||
}
|
||||
}
|
||||
|
@ -504,25 +145,27 @@ func (o JWT) getClaims(tokenStr string, skipExpiration bool) (*Claims, error) {
|
|||
|
||||
claims, ok := jwtToken.Claims.(*Claims)
|
||||
if !ok {
|
||||
// no need to use a static error, this should never happen
|
||||
log.Debugf("api/auth: expected *Claims, got %T", jwtToken.Claims)
|
||||
log.Debugf("jwt error: expected *Claims, got %T", jwtToken.Claims)
|
||||
return nil, errors.New("got strange claims")
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
//Halt closes any db connection.
|
||||
func (o JWT) Halt() {
|
||||
if o.Postgres != (Postgres{}) && o.Postgres.DB != nil {
|
||||
err := o.Postgres.DB.Close()
|
||||
if err != nil {
|
||||
log.Errorf("JWT cleanup error: %s", err)
|
||||
}
|
||||
} else if o.Mysql != (Mysql{}) && o.Mysql.DB != nil {
|
||||
err := o.Mysql.DB.Close()
|
||||
if err != nil {
|
||||
log.Errorf("JWT cleanup error: %s", err)
|
||||
}
|
||||
func getUsernameFromClaims(options tokenOptions, claims *Claims) string {
|
||||
if options.userField == "Username" {
|
||||
return claims.Username
|
||||
}
|
||||
|
||||
return claims.Subject
|
||||
}
|
||||
|
||||
func getUsernameForToken(options tokenOptions, tokenStr string, skipExpiration bool) (string, error) {
|
||||
claims, err := getJWTClaims(options.secret, tokenStr, skipExpiration)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return getUsernameFromClaims(options, claims), nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
package backends
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/iegomez/mosquitto-go-auth/backends/js"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type jsJWTChecker struct {
|
||||
stackDepthLimit int
|
||||
msMaxDuration int64
|
||||
|
||||
userScript string
|
||||
superuserScript string
|
||||
aclScript string
|
||||
|
||||
options tokenOptions
|
||||
|
||||
runner *js.Runner
|
||||
}
|
||||
|
||||
func NewJsJWTChecker(authOpts map[string]string, options tokenOptions) (jwtChecker, error) {
|
||||
checker := &jsJWTChecker{
|
||||
stackDepthLimit: js.DefaultStackDepthLimit,
|
||||
msMaxDuration: js.DefaultMsMaxDuration,
|
||||
options: options,
|
||||
}
|
||||
|
||||
if stackLimit, ok := authOpts["jwt_js_stack_depth_limit"]; ok {
|
||||
limit, err := strconv.ParseInt(stackLimit, 10, 64)
|
||||
if err != nil {
|
||||
log.Errorf("invalid stack depth limit %s, defaulting to %d", stackLimit, js.DefaultStackDepthLimit)
|
||||
} else {
|
||||
checker.stackDepthLimit = int(limit)
|
||||
}
|
||||
}
|
||||
|
||||
if maxDuration, ok := authOpts["jwt_js_ms_max_duration"]; ok {
|
||||
duration, err := strconv.ParseInt(maxDuration, 10, 64)
|
||||
if err != nil {
|
||||
log.Errorf("invalid stack depth limit %s, defaulting to %d", maxDuration, js.DefaultMsMaxDuration)
|
||||
} else {
|
||||
checker.msMaxDuration = duration
|
||||
}
|
||||
}
|
||||
|
||||
if userScriptPath, ok := authOpts["jwt_js_user_script_path"]; ok {
|
||||
script, err := js.LoadScript(userScriptPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
checker.userScript = script
|
||||
} else {
|
||||
return nil, errors.New("missing jwt_js_user_script_path")
|
||||
}
|
||||
|
||||
if superuserScriptPath, ok := authOpts["jwt_js_superuser_script_path"]; ok {
|
||||
script, err := js.LoadScript(superuserScriptPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
checker.superuserScript = script
|
||||
} else {
|
||||
return nil, errors.New("missing jwt_js_superuser_script_path")
|
||||
}
|
||||
|
||||
if aclScriptPath, ok := authOpts["jwt_js_acl_script_path"]; ok {
|
||||
script, err := js.LoadScript(aclScriptPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
checker.aclScript = script
|
||||
} else {
|
||||
return nil, errors.New("missing jwt_js_acl_script_path")
|
||||
}
|
||||
|
||||
checker.runner = js.NewRunner(checker.stackDepthLimit, checker.msMaxDuration)
|
||||
|
||||
return checker, nil
|
||||
}
|
||||
|
||||
func (o *jsJWTChecker) GetUser(token string) (bool, error) {
|
||||
params := map[string]interface{}{
|
||||
"token": token,
|
||||
}
|
||||
|
||||
if o.options.parseToken {
|
||||
username, err := getUsernameForToken(o.options, token, o.options.skipUserExpiration)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("jwt get user error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
params["username"] = username
|
||||
}
|
||||
|
||||
granted, err := o.runner.RunScript(o.userScript, params)
|
||||
if err != nil {
|
||||
log.Errorf("js error: %s", err)
|
||||
}
|
||||
|
||||
return granted, err
|
||||
}
|
||||
|
||||
func (o *jsJWTChecker) GetSuperuser(token string) (bool, error) {
|
||||
params := map[string]interface{}{
|
||||
"token": token,
|
||||
}
|
||||
|
||||
if o.options.parseToken {
|
||||
username, err := getUsernameForToken(o.options, token, o.options.skipUserExpiration)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("jwt get user error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
params["username"] = username
|
||||
}
|
||||
|
||||
granted, err := o.runner.RunScript(o.superuserScript, params)
|
||||
if err != nil {
|
||||
log.Errorf("js error: %s", err)
|
||||
}
|
||||
|
||||
return granted, err
|
||||
}
|
||||
|
||||
func (o *jsJWTChecker) CheckAcl(token, topic, clientid string, acc int32) (bool, error) {
|
||||
params := map[string]interface{}{
|
||||
"token": token,
|
||||
"topic": topic,
|
||||
"clientid": clientid,
|
||||
"acc": acc,
|
||||
}
|
||||
|
||||
if o.options.parseToken {
|
||||
username, err := getUsernameForToken(o.options, token, o.options.skipACLExpiration)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("jwt get user error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
params["username"] = username
|
||||
}
|
||||
|
||||
granted, err := o.runner.RunScript(o.aclScript, params)
|
||||
if err != nil {
|
||||
log.Errorf("js error: %s", err)
|
||||
}
|
||||
|
||||
return granted, err
|
||||
}
|
||||
|
||||
func (o *jsJWTChecker) Halt() {
|
||||
// NO-OP
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
package backends
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"strings"
|
||||
|
||||
"github.com/iegomez/mosquitto-go-auth/hashing"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type localJWTChecker struct {
|
||||
db string
|
||||
postgres Postgres
|
||||
mysql Mysql
|
||||
userQuery string
|
||||
hasher hashing.HashComparer
|
||||
options tokenOptions
|
||||
}
|
||||
|
||||
const (
|
||||
mysqlDB = "mysql"
|
||||
postgresDB = "postgres"
|
||||
)
|
||||
|
||||
// NewLocalJWTChecker initializes a checker with a local DB.
|
||||
func NewLocalJWTChecker(authOpts map[string]string, logLevel log.Level, hasher hashing.HashComparer, options tokenOptions) (jwtChecker, error) {
|
||||
checker := &localJWTChecker{
|
||||
hasher: hasher,
|
||||
db: postgresDB,
|
||||
options: options,
|
||||
}
|
||||
|
||||
missingOpts := ""
|
||||
localOk := true
|
||||
|
||||
if options.secret == "" {
|
||||
return nil, errors.New("JWT backend error: missing jwt secret")
|
||||
}
|
||||
|
||||
if db, ok := authOpts["jwt_db"]; ok {
|
||||
checker.db = db
|
||||
}
|
||||
|
||||
if userQuery, ok := authOpts["jwt_userquery"]; ok {
|
||||
checker.userQuery = userQuery
|
||||
} else {
|
||||
localOk = false
|
||||
missingOpts += " jwt_userquery"
|
||||
}
|
||||
|
||||
if !localOk {
|
||||
return nil, errors.Errorf("JWT backend error: missing local options: %s", missingOpts)
|
||||
}
|
||||
|
||||
// Extract DB specific opts (e.g., host, port, etc.) to construct the underlying DB backend.
|
||||
dbAuthOpts := extractOpts(authOpts, checker.db)
|
||||
|
||||
if checker.db == mysqlDB {
|
||||
mysql, err := NewMysql(dbAuthOpts, logLevel, hasher)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("JWT backend error: couldn't create mysql connector for local jwt: %s", err)
|
||||
}
|
||||
|
||||
checker.mysql = mysql
|
||||
|
||||
return checker, nil
|
||||
}
|
||||
|
||||
postgres, err := NewPostgres(dbAuthOpts, logLevel, hasher)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("JWT backend error: couldn't create postgres connector for local jwt: %s", err)
|
||||
}
|
||||
|
||||
checker.postgres = postgres
|
||||
|
||||
return checker, nil
|
||||
}
|
||||
|
||||
func (o *localJWTChecker) GetUser(token string) (bool, error) {
|
||||
username, err := getUsernameForToken(o.options, token, o.options.skipUserExpiration)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("jwt local get user error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
return o.getLocalUser(username)
|
||||
}
|
||||
|
||||
func (o *localJWTChecker) GetSuperuser(token string) (bool, error) {
|
||||
username, err := getUsernameForToken(o.options, token, o.options.skipUserExpiration)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("jwt local get superuser error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if o.db == mysqlDB {
|
||||
return o.mysql.GetSuperuser(username)
|
||||
}
|
||||
|
||||
return o.postgres.GetSuperuser(username)
|
||||
}
|
||||
|
||||
func (o *localJWTChecker) CheckAcl(token, topic, clientid string, acc int32) (bool, error) {
|
||||
username, err := getUsernameForToken(o.options, token, o.options.skipACLExpiration)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("jwt local check acl error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if o.db == mysqlDB {
|
||||
return o.mysql.CheckAcl(username, topic, clientid, acc)
|
||||
}
|
||||
|
||||
return o.postgres.CheckAcl(username, topic, clientid, acc)
|
||||
}
|
||||
|
||||
func (o *localJWTChecker) Halt() {
|
||||
if o.postgres != (Postgres{}) && o.postgres.DB != nil {
|
||||
err := o.postgres.DB.Close()
|
||||
if err != nil {
|
||||
log.Errorf("JWT cleanup error: %s", err)
|
||||
}
|
||||
} else if o.mysql != (Mysql{}) && o.mysql.DB != nil {
|
||||
err := o.mysql.DB.Close()
|
||||
if err != nil {
|
||||
log.Errorf("JWT cleanup error: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *localJWTChecker) getLocalUser(username string) (bool, error) {
|
||||
if o.userQuery == "" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var count sql.NullInt64
|
||||
var err error
|
||||
if o.db == mysqlDB {
|
||||
err = o.mysql.DB.Get(&count, o.userQuery, username)
|
||||
} else {
|
||||
err = o.postgres.DB.Get(&count, o.userQuery, username)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Debugf("local JWT get user error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !count.Valid {
|
||||
log.Debugf("local JWT get user error: user %s not found", username)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if count.Int64 > 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func extractOpts(authOpts map[string]string, db string) map[string]string {
|
||||
dbAuthOpts := make(map[string]string)
|
||||
|
||||
dbPrefix := "pg"
|
||||
if db == mysqlDB {
|
||||
dbPrefix = mysqlDB
|
||||
}
|
||||
|
||||
prefix := "jwt_" + dbPrefix
|
||||
|
||||
for k, v := range authOpts {
|
||||
if strings.HasPrefix(k, prefix) {
|
||||
dbAuthOpts[strings.TrimPrefix(k, "jwt_")] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Set a dummy query for user check since it won't be checked with the DB backend's method.
|
||||
dbAuthOpts[dbPrefix+"_userquery"] = "dummy"
|
||||
|
||||
return dbAuthOpts
|
||||
}
|
|
@ -0,0 +1,308 @@
|
|||
package backends
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
h "net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type remoteJWTChecker struct {
|
||||
userUri string
|
||||
superuserUri string
|
||||
aclUri string
|
||||
host string
|
||||
port string
|
||||
withTLS bool
|
||||
verifyPeer bool
|
||||
|
||||
paramsMode string
|
||||
responseMode string
|
||||
|
||||
options tokenOptions
|
||||
|
||||
client *h.Client
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Ok bool `json:"ok"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func NewRemoteJWTChecker(authOpts map[string]string, options tokenOptions) (jwtChecker, error) {
|
||||
var checker = &remoteJWTChecker{
|
||||
withTLS: false,
|
||||
verifyPeer: false,
|
||||
responseMode: "status",
|
||||
paramsMode: "json",
|
||||
options: options,
|
||||
}
|
||||
|
||||
missingOpts := ""
|
||||
remoteOk := true
|
||||
|
||||
if responseMode, ok := authOpts["jwt_response_mode"]; ok {
|
||||
if responseMode == "text" || responseMode == "json" {
|
||||
checker.responseMode = responseMode
|
||||
}
|
||||
}
|
||||
|
||||
if paramsMode, ok := authOpts["jwt_params_mode"]; ok {
|
||||
if paramsMode == "form" {
|
||||
checker.paramsMode = paramsMode
|
||||
}
|
||||
}
|
||||
|
||||
if userUri, ok := authOpts["jwt_getuser_uri"]; ok {
|
||||
checker.userUri = userUri
|
||||
} else {
|
||||
remoteOk = false
|
||||
missingOpts += " jwt_getuser_uri"
|
||||
}
|
||||
|
||||
if superuserUri, ok := authOpts["jwt_superuser_uri"]; ok {
|
||||
checker.superuserUri = superuserUri
|
||||
}
|
||||
|
||||
if aclUri, ok := authOpts["jwt_aclcheck_uri"]; ok {
|
||||
checker.aclUri = aclUri
|
||||
} else {
|
||||
remoteOk = false
|
||||
missingOpts += " jwt_aclcheck_uri"
|
||||
}
|
||||
|
||||
if hostname, ok := authOpts["jwt_host"]; ok {
|
||||
checker.host = hostname
|
||||
} else {
|
||||
remoteOk = false
|
||||
missingOpts += " jwt_host"
|
||||
}
|
||||
|
||||
if port, ok := authOpts["jwt_port"]; ok {
|
||||
checker.port = port
|
||||
} else {
|
||||
remoteOk = false
|
||||
missingOpts += " jwt_port"
|
||||
}
|
||||
|
||||
if withTLS, ok := authOpts["jwt_with_tls"]; ok && withTLS == "true" {
|
||||
checker.withTLS = true
|
||||
}
|
||||
|
||||
if verifyPeer, ok := authOpts["jwt_verify_peer"]; ok && verifyPeer == "true" {
|
||||
checker.verifyPeer = true
|
||||
}
|
||||
|
||||
if !remoteOk {
|
||||
return nil, errors.Errorf("JWT backend error: missing remote options: %s", missingOpts)
|
||||
}
|
||||
|
||||
checker.client = &h.Client{Timeout: 5 * time.Second}
|
||||
|
||||
if !checker.verifyPeer {
|
||||
tr := &h.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
checker.client.Transport = tr
|
||||
}
|
||||
|
||||
return checker, nil
|
||||
}
|
||||
|
||||
func (o *remoteJWTChecker) GetUser(token string) (bool, error) {
|
||||
var dataMap map[string]interface{}
|
||||
var urlValues url.Values
|
||||
|
||||
if o.options.parseToken {
|
||||
username, err := getUsernameForToken(o.options, token, o.options.skipUserExpiration)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("jwt remote get user error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
dataMap = map[string]interface{}{
|
||||
"username": username,
|
||||
}
|
||||
|
||||
urlValues = url.Values{
|
||||
"username": []string{username},
|
||||
}
|
||||
}
|
||||
|
||||
return o.jwtRequest(o.host, o.userUri, token, dataMap, urlValues)
|
||||
}
|
||||
|
||||
func (o *remoteJWTChecker) GetSuperuser(token string) (bool, error) {
|
||||
if o.superuserUri == "" {
|
||||
return false, nil
|
||||
}
|
||||
var dataMap map[string]interface{}
|
||||
var urlValues = url.Values{}
|
||||
|
||||
if o.options.parseToken {
|
||||
username, err := getUsernameForToken(o.options, token, o.options.skipUserExpiration)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("jwt remote get superuser error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
dataMap = map[string]interface{}{
|
||||
"username": username,
|
||||
}
|
||||
|
||||
urlValues = url.Values{
|
||||
"username": []string{username},
|
||||
}
|
||||
}
|
||||
|
||||
return o.jwtRequest(o.host, o.superuserUri, token, dataMap, urlValues)
|
||||
}
|
||||
|
||||
func (o *remoteJWTChecker) CheckAcl(token, topic, clientid string, acc int32) (bool, error) {
|
||||
dataMap := map[string]interface{}{
|
||||
"clientid": clientid,
|
||||
"topic": topic,
|
||||
"acc": acc,
|
||||
}
|
||||
var urlValues = url.Values{
|
||||
"clientid": []string{clientid},
|
||||
"topic": []string{topic},
|
||||
"acc": []string{strconv.Itoa(int(acc))},
|
||||
}
|
||||
|
||||
if o.options.parseToken {
|
||||
username, err := getUsernameForToken(o.options, token, o.options.skipACLExpiration)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("jwt remote check acl error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
dataMap["username"] = username
|
||||
|
||||
urlValues.Add("username", username)
|
||||
}
|
||||
|
||||
return o.jwtRequest(o.host, o.aclUri, token, dataMap, urlValues)
|
||||
}
|
||||
|
||||
func (o *remoteJWTChecker) Halt() {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
func (o *remoteJWTChecker) jwtRequest(host, uri, token string, dataMap map[string]interface{}, urlValues url.Values) (bool, error) {
|
||||
|
||||
// Don't do the request if the client is nil.
|
||||
if o.client == nil {
|
||||
return false, errors.New("client not initilized")
|
||||
}
|
||||
|
||||
tlsStr := "http://"
|
||||
|
||||
if o.withTLS {
|
||||
tlsStr = "https://"
|
||||
}
|
||||
|
||||
fullURI := fmt.Sprintf("%s%s%s", tlsStr, o.host, uri)
|
||||
if o.port != "" {
|
||||
fullURI = fmt.Sprintf("%s%s:%s%s", tlsStr, o.host, o.port, uri)
|
||||
}
|
||||
|
||||
var resp *h.Response
|
||||
var err error
|
||||
var req *h.Request
|
||||
|
||||
switch o.paramsMode {
|
||||
case "json":
|
||||
dataJSON, err := json.Marshal(dataMap)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("marshal error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
contentReader := bytes.NewReader(dataJSON)
|
||||
req, err = h.NewRequest("POST", fullURI, contentReader)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("req error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
default:
|
||||
req, err = h.NewRequest("POST", fullURI, strings.NewReader(urlValues.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Content-Length", strconv.Itoa(len(urlValues.Encode())))
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("req error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
|
||||
resp, err = o.client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("error: %v", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("read error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
log.Infof("error code: %d", resp.StatusCode)
|
||||
if resp.StatusCode >= 500 {
|
||||
err = fmt.Errorf("error code: %d", resp.StatusCode)
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
if o.responseMode == "text" {
|
||||
|
||||
//For test response, we expect "ok" or an error message.
|
||||
if string(body) != "ok" {
|
||||
log.Infof("api error: %s", string(body))
|
||||
return false, nil
|
||||
}
|
||||
|
||||
} else if o.responseMode == "json" {
|
||||
|
||||
//For json response, we expect Ok and Error fields.
|
||||
response := Response{Ok: false, Error: ""}
|
||||
err = json.Unmarshal(body, &response)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("unmarshal error: %s", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !response.Ok {
|
||||
log.Infof("api error: %s", response.Error)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
log.Debugf("jwt request approved for %s", token)
|
||||
return true, nil
|
||||
}
|
|
@ -55,36 +55,18 @@ var expiredToken = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
|||
"username": username,
|
||||
})
|
||||
|
||||
var tkOptions = tokenOptions{
|
||||
secret: jwtSecret,
|
||||
userField: "Username",
|
||||
}
|
||||
|
||||
func TestJWTClaims(t *testing.T) {
|
||||
Convey("Correct token should give no errors", t, func() {
|
||||
// Initialize JWT in local mode.
|
||||
authOpts := make(map[string]string)
|
||||
authOpts["jwt_remote"] = "false"
|
||||
authOpts["jwt_db"] = "postgres"
|
||||
authOpts["jwt_secret"] = jwtSecret
|
||||
authOpts["jwt_userquery"] = "select count(*) from test_user where username = $1 limit 1"
|
||||
authOpts["jwt_superquery"] = "select count(*) from test_user where username = $1 and is_admin = true"
|
||||
authOpts["jwt_aclquery"] = "SELECT test_acl.topic FROM test_acl, test_user WHERE test_user.username = $1 AND test_acl.test_user_id = test_user.id AND rw >= $2"
|
||||
authOpts["pg_userquery"] = "mock_string"
|
||||
authOpts["pg_superquery"] = "mock_string"
|
||||
authOpts["pg_aclquery"] = "mock_string"
|
||||
authOpts["jwt_userfield"] = "Username"
|
||||
|
||||
//Give necessary postgres options.
|
||||
authOpts["pg_host"] = "localhost"
|
||||
authOpts["pg_port"] = "5432"
|
||||
authOpts["pg_dbname"] = "go_auth_test"
|
||||
authOpts["pg_user"] = "go_auth_test"
|
||||
authOpts["pg_password"] = "go_auth_test"
|
||||
|
||||
jwt, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("When getting claims", t, func() {
|
||||
Convey("Correct token should give no error", func() {
|
||||
token, err := jwtToken.SignedString([]byte(jwtSecret))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = jwt.getClaims(token, false)
|
||||
_, err = getJWTClaims(jwtSecret, token, false)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
|
@ -92,7 +74,7 @@ func TestJWTClaims(t *testing.T) {
|
|||
token, err := jwtToken.SignedString([]byte("wrong-secret"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = jwt.getClaims(token, false)
|
||||
_, err = getJWTClaims(jwtSecret, token, false)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
|
@ -100,7 +82,7 @@ func TestJWTClaims(t *testing.T) {
|
|||
token, err := wrongJwtToken.SignedString([]byte(jwtSecret))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = jwt.getClaims(token, false)
|
||||
_, err = getJWTClaims(jwtSecret, token, false)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
|
@ -108,23 +90,89 @@ func TestJWTClaims(t *testing.T) {
|
|||
token, err := expiredToken.SignedString([]byte(jwtSecret))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = jwt.getClaims(token, false)
|
||||
_, err = getJWTClaims(jwtSecret, token, false)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("When setting skip expiration, expired token should not give an error", func() {
|
||||
jwt, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("When skipping expiration, expired token should not give an error", func() {
|
||||
token, err := expiredToken.SignedString([]byte(jwtSecret))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = jwt.getClaims(token, true)
|
||||
_, err = getJWTClaims(jwtSecret, token, true)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestJsJWTChecker(t *testing.T) {
|
||||
authOpts := make(map[string]string)
|
||||
|
||||
authOpts["jwt_js_user_script_path"] = "../test-files/jwt/user_script.js"
|
||||
authOpts["jwt_js_superuser_script_path"] = "../test-files/jwt/superuser_script.js"
|
||||
authOpts["jwt_js_acl_script_path"] = "../test-files/jwt/acl_script.js"
|
||||
|
||||
Convey("Creating a js checker should succeed", t, func() {
|
||||
checker, err := NewJsJWTChecker(authOpts, tkOptions)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
userResponse, err := checker.GetUser("correct")
|
||||
So(err, ShouldBeNil)
|
||||
So(userResponse, ShouldBeTrue)
|
||||
|
||||
userResponse, err = checker.GetUser("bad")
|
||||
So(err, ShouldBeNil)
|
||||
So(userResponse, ShouldBeFalse)
|
||||
|
||||
superuserResponse, err := checker.GetSuperuser("admin")
|
||||
So(err, ShouldBeNil)
|
||||
So(superuserResponse, ShouldBeTrue)
|
||||
|
||||
superuserResponse, err = checker.GetSuperuser("non-admin")
|
||||
So(err, ShouldBeNil)
|
||||
So(superuserResponse, ShouldBeFalse)
|
||||
|
||||
aclResponse, err := checker.CheckAcl("correct", "test/topic", "id", 1)
|
||||
So(err, ShouldBeNil)
|
||||
So(aclResponse, ShouldBeTrue)
|
||||
|
||||
aclResponse, err = checker.CheckAcl("incorrect", "test/topic", "id", 1)
|
||||
So(err, ShouldBeNil)
|
||||
So(userResponse, ShouldBeFalse)
|
||||
|
||||
aclResponse, err = checker.CheckAcl("correct", "bad/topic", "id", 1)
|
||||
So(err, ShouldBeNil)
|
||||
So(aclResponse, ShouldBeFalse)
|
||||
|
||||
aclResponse, err = checker.CheckAcl("correct", "test/topic", "wrong-id", 1)
|
||||
So(err, ShouldBeNil)
|
||||
So(aclResponse, ShouldBeFalse)
|
||||
|
||||
aclResponse, err = checker.CheckAcl("correct", "test/topic", "id", 2)
|
||||
So(err, ShouldBeNil)
|
||||
So(aclResponse, ShouldBeFalse)
|
||||
|
||||
Convey("Tokens may be pre-parsed and passed to the scripts", func() {
|
||||
jsTokenOptions := tokenOptions{
|
||||
parseToken: true,
|
||||
secret: jwtSecret,
|
||||
userField: "Username",
|
||||
}
|
||||
|
||||
authOpts["jwt_js_user_script_path"] = "../test-files/jwt/parsed_user_script.js"
|
||||
|
||||
checker, err = NewJsJWTChecker(authOpts, jsTokenOptions)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
token, err := jwtToken.SignedString([]byte(jwtSecret))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
userResponse, err := checker.GetUser(token)
|
||||
So(err, ShouldBeNil)
|
||||
So(userResponse, ShouldBeTrue)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestLocalPostgresJWT(t *testing.T) {
|
||||
|
||||
Convey("Creating a token should return a nil error", t, func() {
|
||||
|
@ -133,31 +181,43 @@ func TestLocalPostgresJWT(t *testing.T) {
|
|||
|
||||
// Initialize JWT in local mode.
|
||||
authOpts := make(map[string]string)
|
||||
authOpts["jwt_remote"] = "false"
|
||||
authOpts["jwt_mode"] = "local"
|
||||
authOpts["jwt_db"] = "postgres"
|
||||
authOpts["jwt_secret"] = jwtSecret
|
||||
authOpts["jwt_userquery"] = "select count(*) from test_user where username = $1 limit 1"
|
||||
authOpts["jwt_superquery"] = "select count(*) from test_user where username = $1 and is_admin = true"
|
||||
authOpts["jwt_aclquery"] = "SELECT test_acl.topic FROM test_acl, test_user WHERE test_user.username = $1 AND test_acl.test_user_id = test_user.id AND rw >= $2"
|
||||
authOpts["pg_userquery"] = "mock_string"
|
||||
authOpts["pg_superquery"] = "mock_string"
|
||||
authOpts["pg_aclquery"] = "mock_string"
|
||||
authOpts["jwt_userfield"] = "Username"
|
||||
authOpts["jwt_userquery"] = "select count(*) from test_user where username = $1 limit 1"
|
||||
|
||||
//Give necessary postgres options.
|
||||
authOpts["pg_host"] = "localhost"
|
||||
authOpts["pg_port"] = "5432"
|
||||
authOpts["pg_dbname"] = "go_auth_test"
|
||||
authOpts["pg_user"] = "go_auth_test"
|
||||
authOpts["pg_password"] = "go_auth_test"
|
||||
// Give necessary postgres options.
|
||||
authOpts["jwt_pg_host"] = "localhost"
|
||||
authOpts["jwt_pg_port"] = "5432"
|
||||
authOpts["jwt_pg_dbname"] = "go_auth_test"
|
||||
authOpts["jwt_pg_user"] = "go_auth_test"
|
||||
authOpts["jwt_pg_password"] = "go_auth_test"
|
||||
authOpts["jwt_pg_superquery"] = "select count(*) from test_user where username = $1 and is_admin = true"
|
||||
authOpts["jwt_pg_aclquery"] = "SELECT test_acl.topic FROM test_acl, test_user WHERE test_user.username = $1 AND test_acl.test_user_id = test_user.id AND rw >= $2"
|
||||
|
||||
// Set regular PG options just to create a PG instance and create the records.
|
||||
|
||||
pgAuthOpts := make(map[string]string)
|
||||
pgAuthOpts["pg_host"] = "localhost"
|
||||
pgAuthOpts["pg_port"] = "5432"
|
||||
pgAuthOpts["pg_dbname"] = "go_auth_test"
|
||||
pgAuthOpts["pg_user"] = "go_auth_test"
|
||||
pgAuthOpts["pg_password"] = "go_auth_test"
|
||||
pgAuthOpts["pg_userquery"] = "mock"
|
||||
pgAuthOpts["pg_superquery"] = "mock"
|
||||
pgAuthOpts["pg_aclquery"] = "mock"
|
||||
|
||||
db, err := NewPostgres(pgAuthOpts, log.DebugLevel, hashing.NewHasher(pgAuthOpts, ""))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Given correct option NewJWT returns an instance of jwt backend", func() {
|
||||
jwt, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""))
|
||||
jwt, err := NewLocalJWTChecker(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), tkOptions)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
//Empty db
|
||||
jwt.Postgres.DB.MustExec("delete from test_user where 1 = 1")
|
||||
jwt.Postgres.DB.MustExec("delete from test_acl where 1 = 1")
|
||||
db.DB.MustExec("delete from test_user where 1 = 1")
|
||||
db.DB.MustExec("delete from test_acl where 1 = 1")
|
||||
|
||||
//Now test everything.
|
||||
|
||||
|
@ -165,17 +225,16 @@ func TestLocalPostgresJWT(t *testing.T) {
|
|||
|
||||
userID := 0
|
||||
|
||||
err = jwt.Postgres.DB.Get(&userID, insertQuery, username, userPassHash, true)
|
||||
err = db.DB.Get(&userID, insertQuery, username, userPassHash, true)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(userID, ShouldBeGreaterThan, 0)
|
||||
|
||||
Convey("Given a correct token, it should correctly authenticate it", func() {
|
||||
|
||||
authenticated, err := jwt.GetUser(token, "", "")
|
||||
authenticated, err := jwt.GetUser(token)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeTrue)
|
||||
|
||||
})
|
||||
|
||||
Convey("Given an incorrect token, it should not authenticate it", func() {
|
||||
|
@ -183,7 +242,7 @@ func TestLocalPostgresJWT(t *testing.T) {
|
|||
wrongToken, err := wrongJwtToken.SignedString([]byte(jwtSecret))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
authenticated, err := jwt.GetUser(wrongToken, "", "")
|
||||
authenticated, err := jwt.GetUser(wrongToken)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeFalse)
|
||||
|
||||
|
@ -193,8 +252,12 @@ func TestLocalPostgresJWT(t *testing.T) {
|
|||
superuser, err := jwt.GetSuperuser(token)
|
||||
So(err, ShouldBeNil)
|
||||
So(superuser, ShouldBeTrue)
|
||||
Convey("But disabling superusers by removing superuserquery should now return false", func() {
|
||||
jwt.SuperuserQuery = ""
|
||||
|
||||
Convey("But disabling superusers by removing superuri should now return false", func() {
|
||||
authOpts["jwt_pg_superquery"] = ""
|
||||
jwt, err := NewLocalJWTChecker(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), tkOptions)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
superuser, err := jwt.GetSuperuser(token)
|
||||
So(err, ShouldBeNil)
|
||||
So(superuser, ShouldBeFalse)
|
||||
|
@ -203,15 +266,15 @@ func TestLocalPostgresJWT(t *testing.T) {
|
|||
|
||||
//Now create some acls and test topics
|
||||
|
||||
strictAcl := "test/topic/1"
|
||||
singleLevelAcl := "test/topic/+"
|
||||
hierarchyAcl := "test/#"
|
||||
strictACL := "test/topic/1"
|
||||
singleLevelACL := "test/topic/+"
|
||||
hierarchyACL := "test/#"
|
||||
|
||||
clientID := "test_client"
|
||||
|
||||
aclID := 0
|
||||
aclQuery := "INSERT INTO test_acl(test_user_id, topic, rw) values($1, $2, $3) returning id"
|
||||
err = jwt.Postgres.DB.Get(&aclID, aclQuery, userID, strictAcl, MOSQ_ACL_READ)
|
||||
err = db.DB.Get(&aclID, aclQuery, userID, strictACL, MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Given only strict acl in db, an exact match should work and and inexact one not", func() {
|
||||
|
@ -240,8 +303,8 @@ func TestLocalPostgresJWT(t *testing.T) {
|
|||
|
||||
Convey("Given wildcard subscriptions against strict db acl, acl checks should fail", func() {
|
||||
|
||||
tt1, err1 := jwt.CheckAcl(token, singleLevelAcl, clientID, MOSQ_ACL_READ)
|
||||
tt2, err2 := jwt.CheckAcl(token, hierarchyAcl, clientID, MOSQ_ACL_READ)
|
||||
tt1, err1 := jwt.CheckAcl(token, singleLevelACL, clientID, MOSQ_ACL_READ)
|
||||
tt2, err2 := jwt.CheckAcl(token, hierarchyACL, clientID, MOSQ_ACL_READ)
|
||||
|
||||
So(err1, ShouldBeNil)
|
||||
So(err2, ShouldBeNil)
|
||||
|
@ -252,7 +315,7 @@ func TestLocalPostgresJWT(t *testing.T) {
|
|||
|
||||
//Now insert single level topic to check against.
|
||||
|
||||
err = jwt.Postgres.DB.Get(&aclID, aclQuery, userID, singleLevelAcl, MOSQ_ACL_READ)
|
||||
err = db.DB.Get(&aclID, aclQuery, userID, singleLevelACL, MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Given a topic not strictly present that matches a db single level wildcard, acl check should pass", func() {
|
||||
|
@ -263,7 +326,7 @@ func TestLocalPostgresJWT(t *testing.T) {
|
|||
|
||||
//Now insert hierarchy wildcard to check against.
|
||||
|
||||
err = jwt.Postgres.DB.Get(&aclID, aclQuery, userID, hierarchyAcl, MOSQ_ACL_READ)
|
||||
err = db.DB.Get(&aclID, aclQuery, userID, hierarchyACL, MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Given a topic not strictly present that matches a hierarchy wildcard, acl check should pass", func() {
|
||||
|
@ -272,9 +335,35 @@ func TestLocalPostgresJWT(t *testing.T) {
|
|||
So(tt1, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Deleting superuser and acl queries should work fine", func() {
|
||||
|
||||
authOpts["jwt_pg_superquery"] = ""
|
||||
authOpts["jwt_pg_aclquery"] = ""
|
||||
|
||||
jwt, err := NewLocalJWTChecker(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), tkOptions)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("So checking against them should give false and true for any user", func() {
|
||||
|
||||
tt1, err1 := jwt.CheckAcl(token, singleLevelACL, clientID, MOSQ_ACL_READ)
|
||||
tt2, err2 := jwt.CheckAcl(token, hierarchyACL, clientID, MOSQ_ACL_READ)
|
||||
|
||||
So(err1, ShouldBeNil)
|
||||
So(err2, ShouldBeNil)
|
||||
So(tt1, ShouldBeTrue)
|
||||
So(tt2, ShouldBeTrue)
|
||||
|
||||
superuser, err := jwt.GetSuperuser(token)
|
||||
So(err, ShouldBeNil)
|
||||
So(superuser, ShouldBeFalse)
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
//Empty db
|
||||
jwt.Postgres.DB.MustExec("delete from test_user where 1 = 1")
|
||||
jwt.Postgres.DB.MustExec("delete from test_acl where 1 = 1")
|
||||
db.DB.MustExec("delete from test_user where 1 = 1")
|
||||
db.DB.MustExec("delete from test_acl where 1 = 1")
|
||||
|
||||
jwt.Halt()
|
||||
})
|
||||
|
@ -289,34 +378,46 @@ func TestLocalMysqlJWT(t *testing.T) {
|
|||
token, err := jwtToken.SignedString([]byte(jwtSecret))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
//Initialize JWT in local mode.
|
||||
// Initialize JWT in local mode.
|
||||
authOpts := make(map[string]string)
|
||||
authOpts["jwt_remote"] = "false"
|
||||
authOpts["jwt_mode"] = "local"
|
||||
authOpts["jwt_db"] = "mysql"
|
||||
authOpts["jwt_secret"] = jwtSecret
|
||||
authOpts["jwt_userquery"] = "select count(*) from test_user where username = ? limit 1"
|
||||
authOpts["jwt_superquery"] = "select count(*) from test_user where username = ? and is_admin = true"
|
||||
authOpts["jwt_aclquery"] = "SELECT test_acl.topic FROM test_acl, test_user WHERE test_user.username = ? AND test_acl.test_user_id = test_user.id AND rw >= ?"
|
||||
authOpts["mysql_userquery"] = "mock_string"
|
||||
authOpts["mysql_superquery"] = "mock_string"
|
||||
authOpts["mysql_aclquery"] = "mock_string"
|
||||
authOpts["jwt_userfield"] = "Username"
|
||||
authOpts["jwt_userquery"] = "select count(*) from test_user where username = ? limit 1"
|
||||
|
||||
//Give necessary postgres options.
|
||||
authOpts["mysql_host"] = "localhost"
|
||||
authOpts["mysql_port"] = "3306"
|
||||
authOpts["mysql_dbname"] = "go_auth_test"
|
||||
authOpts["mysql_user"] = "go_auth_test"
|
||||
authOpts["mysql_password"] = "go_auth_test"
|
||||
authOpts["mysql_allow_native_passwords"] = "true"
|
||||
// Give necessary postgres options.
|
||||
authOpts["jwt_mysql_host"] = "localhost"
|
||||
authOpts["jwt_mysql_port"] = "3306"
|
||||
authOpts["jwt_mysql_dbname"] = "go_auth_test"
|
||||
authOpts["jwt_mysql_user"] = "go_auth_test"
|
||||
authOpts["jwt_mysql_password"] = "go_auth_test"
|
||||
authOpts["jwt_mysql_allow_native_passwords"] = "true"
|
||||
authOpts["jwt_mysql_superquery"] = "select count(*) from test_user where username = ? and is_admin = true"
|
||||
authOpts["jwt_mysql_aclquery"] = "SELECT test_acl.topic FROM test_acl, test_user WHERE test_user.username = ? AND test_acl.test_user_id = test_user.id AND rw >= ?"
|
||||
|
||||
// Set options for our MySQL instance used to create test records.
|
||||
mysqlAuthOpts := make(map[string]string)
|
||||
mysqlAuthOpts["mysql_host"] = "localhost"
|
||||
mysqlAuthOpts["mysql_port"] = "3306"
|
||||
mysqlAuthOpts["mysql_dbname"] = "go_auth_test"
|
||||
mysqlAuthOpts["mysql_user"] = "go_auth_test"
|
||||
mysqlAuthOpts["mysql_password"] = "go_auth_test"
|
||||
mysqlAuthOpts["mysql_allow_native_passwords"] = "true"
|
||||
mysqlAuthOpts["mysql_userquery"] = "mock"
|
||||
mysqlAuthOpts["mysql_superquery"] = "mock"
|
||||
mysqlAuthOpts["mysql_aclquery"] = "mock"
|
||||
|
||||
db, err := NewMysql(mysqlAuthOpts, log.DebugLevel, hashing.NewHasher(mysqlAuthOpts, ""))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Given correct option NewJWT returns an instance of jwt backend", func() {
|
||||
jwt, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""))
|
||||
jwt, err := NewLocalJWTChecker(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), tkOptions)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
//Empty db
|
||||
jwt.Mysql.DB.MustExec("delete from test_user where 1 = 1")
|
||||
jwt.Mysql.DB.MustExec("delete from test_acl where 1 = 1")
|
||||
db.DB.MustExec("delete from test_user where 1 = 1")
|
||||
db.DB.MustExec("delete from test_acl where 1 = 1")
|
||||
|
||||
//Now test everything.
|
||||
|
||||
|
@ -324,7 +425,7 @@ func TestLocalMysqlJWT(t *testing.T) {
|
|||
|
||||
userID := int64(0)
|
||||
|
||||
res, err := jwt.Mysql.DB.Exec(insertQuery, username, userPassHash, true)
|
||||
res, err := db.DB.Exec(insertQuery, username, userPassHash, true)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
userID, err = res.LastInsertId()
|
||||
|
@ -334,7 +435,7 @@ func TestLocalMysqlJWT(t *testing.T) {
|
|||
|
||||
Convey("Given a correct token, it should correctly authenticate it", func() {
|
||||
|
||||
authenticated, err := jwt.GetUser(token, "", "")
|
||||
authenticated, err := jwt.GetUser(token)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeTrue)
|
||||
|
||||
|
@ -345,7 +446,7 @@ func TestLocalMysqlJWT(t *testing.T) {
|
|||
wrongToken, err := wrongJwtToken.SignedString([]byte(jwtSecret))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
authenticated, err := jwt.GetUser(wrongToken, "", "")
|
||||
authenticated, err := jwt.GetUser(wrongToken)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeFalse)
|
||||
|
||||
|
@ -355,25 +456,26 @@ func TestLocalMysqlJWT(t *testing.T) {
|
|||
superuser, err := jwt.GetSuperuser(token)
|
||||
So(err, ShouldBeNil)
|
||||
So(superuser, ShouldBeTrue)
|
||||
Convey("But disabling superusers by removing superuserquery should now return false", func() {
|
||||
jwt.SuperuserQuery = ""
|
||||
Convey("But disabling superusers by removing superuri should now return false", func() {
|
||||
authOpts["jwt_mysql_superquery"] = ""
|
||||
jwt, err := NewLocalJWTChecker(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), tkOptions)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
superuser, err := jwt.GetSuperuser(token)
|
||||
So(err, ShouldBeNil)
|
||||
So(superuser, ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
|
||||
//Now create some acls and test topics
|
||||
|
||||
strictAcl := "test/topic/1"
|
||||
singleLevelAcl := "test/topic/+"
|
||||
hierarchyAcl := "test/#"
|
||||
strictACL := "test/topic/1"
|
||||
singleLevelACL := "test/topic/+"
|
||||
hierarchyACL := "test/#"
|
||||
|
||||
clientID := "test_client"
|
||||
|
||||
aclID := int64(0)
|
||||
aclQuery := "INSERT INTO test_acl(test_user_id, topic, rw) values(?, ?, ?)"
|
||||
res, err = jwt.Mysql.DB.Exec(aclQuery, userID, strictAcl, MOSQ_ACL_READ)
|
||||
res, err = db.DB.Exec(aclQuery, userID, strictACL, MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
aclID, err = res.LastInsertId()
|
||||
So(err, ShouldBeNil)
|
||||
|
@ -405,8 +507,8 @@ func TestLocalMysqlJWT(t *testing.T) {
|
|||
|
||||
Convey("Given wildcard subscriptions against strict db acl, acl checks should fail", func() {
|
||||
|
||||
tt1, err1 := jwt.CheckAcl(token, singleLevelAcl, clientID, MOSQ_ACL_READ)
|
||||
tt2, err2 := jwt.CheckAcl(token, hierarchyAcl, clientID, MOSQ_ACL_READ)
|
||||
tt1, err1 := jwt.CheckAcl(token, singleLevelACL, clientID, MOSQ_ACL_READ)
|
||||
tt2, err2 := jwt.CheckAcl(token, hierarchyACL, clientID, MOSQ_ACL_READ)
|
||||
|
||||
So(err1, ShouldBeNil)
|
||||
So(err2, ShouldBeNil)
|
||||
|
@ -417,7 +519,7 @@ func TestLocalMysqlJWT(t *testing.T) {
|
|||
|
||||
//Now insert single level topic to check against.
|
||||
|
||||
_, err = jwt.Mysql.DB.Exec(aclQuery, userID, singleLevelAcl, MOSQ_ACL_READ)
|
||||
_, err = db.DB.Exec(aclQuery, userID, singleLevelACL, MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Given a topic not strictly present that matches a db single level wildcard, acl check should pass", func() {
|
||||
|
@ -428,7 +530,7 @@ func TestLocalMysqlJWT(t *testing.T) {
|
|||
|
||||
//Now insert hierarchy wildcard to check against.
|
||||
|
||||
_, err = jwt.Mysql.DB.Exec(aclQuery, userID, hierarchyAcl, MOSQ_ACL_READ)
|
||||
_, err = db.DB.Exec(aclQuery, userID, hierarchyACL, MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Given a topic not strictly present that matches a hierarchy wildcard, acl check should pass", func() {
|
||||
|
@ -437,19 +539,18 @@ func TestLocalMysqlJWT(t *testing.T) {
|
|||
So(tt1, ShouldBeTrue)
|
||||
})
|
||||
|
||||
//Empty db
|
||||
jwt.Mysql.DB.MustExec("delete from test_user where 1 = 1")
|
||||
jwt.Mysql.DB.MustExec("delete from test_acl where 1 = 1")
|
||||
|
||||
Convey("Deleting superuser and acl queries should work fine", func() {
|
||||
|
||||
jwt.SuperuserQuery = ""
|
||||
jwt.AclQuery = ""
|
||||
authOpts["jwt_mysql_superquery"] = ""
|
||||
authOpts["jwt_mysql_aclquery"] = ""
|
||||
|
||||
jwt, err := NewLocalJWTChecker(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), tkOptions)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("So checking against them should give false and true for any user", func() {
|
||||
|
||||
tt1, err1 := jwt.CheckAcl(token, singleLevelAcl, clientID, MOSQ_ACL_READ)
|
||||
tt2, err2 := jwt.CheckAcl(token, hierarchyAcl, clientID, MOSQ_ACL_READ)
|
||||
tt1, err1 := jwt.CheckAcl(token, singleLevelACL, clientID, MOSQ_ACL_READ)
|
||||
tt2, err2 := jwt.CheckAcl(token, hierarchyACL, clientID, MOSQ_ACL_READ)
|
||||
|
||||
So(err1, ShouldBeNil)
|
||||
So(err2, ShouldBeNil)
|
||||
|
@ -464,6 +565,10 @@ func TestLocalMysqlJWT(t *testing.T) {
|
|||
|
||||
})
|
||||
|
||||
//Empty db
|
||||
db.DB.MustExec("delete from test_user where 1 = 1")
|
||||
db.DB.MustExec("delete from test_acl where 1 = 1")
|
||||
|
||||
jwt.Halt()
|
||||
|
||||
})
|
||||
|
@ -476,7 +581,7 @@ func TestJWTAllJsonServer(t *testing.T) {
|
|||
|
||||
topic := "test/topic"
|
||||
var acc = int64(1)
|
||||
clientId := "test_client"
|
||||
clientID := "test_client"
|
||||
|
||||
token, _ := jwtToken.SignedString([]byte(jwtSecret))
|
||||
wrongToken, _ := wrongJwtToken.SignedString([]byte(jwtSecret))
|
||||
|
@ -522,7 +627,7 @@ func TestJWTAllJsonServer(t *testing.T) {
|
|||
params = data.(map[string]interface{})
|
||||
paramsAcc := int64(params["acc"].(float64))
|
||||
|
||||
if params["topic"].(string) == topic && params["clientid"].(string) == clientId && paramsAcc <= acc {
|
||||
if params["topic"].(string) == topic && params["clientid"].(string) == clientID && paramsAcc <= acc {
|
||||
httpResponse.Ok = true
|
||||
httpResponse.Error = ""
|
||||
break
|
||||
|
@ -544,7 +649,7 @@ func TestJWTAllJsonServer(t *testing.T) {
|
|||
defer mockServer.Close()
|
||||
|
||||
authOpts := make(map[string]string)
|
||||
authOpts["jwt_remote"] = "true"
|
||||
authOpts["jwt_mode"] = "remote"
|
||||
authOpts["jwt_params_mode"] = "json"
|
||||
authOpts["jwt_response_mode"] = "json"
|
||||
authOpts["jwt_host"] = strings.Replace(mockServer.URL, "http://", "", -1)
|
||||
|
@ -580,7 +685,10 @@ func TestJWTAllJsonServer(t *testing.T) {
|
|||
So(authenticated, ShouldBeTrue)
|
||||
|
||||
Convey("But disabling superusers by removing superuri should now return false", func() {
|
||||
hb.SuperuserUri = ""
|
||||
authOpts["jwt_superuser_uri"] = ""
|
||||
hb, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
superuser, err := hb.GetSuperuser(username)
|
||||
So(err, ShouldBeNil)
|
||||
So(superuser, ShouldBeFalse)
|
||||
|
@ -598,7 +706,7 @@ func TestJWTAllJsonServer(t *testing.T) {
|
|||
|
||||
Convey("Given correct topic, username, client id and acc, acl check should return true", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientId, MOSQ_ACL_READ)
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeTrue)
|
||||
|
||||
|
@ -606,7 +714,7 @@ func TestJWTAllJsonServer(t *testing.T) {
|
|||
|
||||
Convey("Given an acc that requires more privileges than the user has, check acl should return false", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientId, MOSQ_ACL_WRITE)
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_WRITE)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeFalse)
|
||||
|
||||
|
@ -614,13 +722,13 @@ func TestJWTAllJsonServer(t *testing.T) {
|
|||
|
||||
Convey("Given a topic not present in acls, check acl should return false", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, "fake/topic", clientId, MOSQ_ACL_READ)
|
||||
authenticated, err := hb.CheckAcl(token, "fake/topic", clientID, MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeFalse)
|
||||
|
||||
})
|
||||
|
||||
Convey("Given a clientId that doesn't match, check acl should return false", func() {
|
||||
Convey("Given a clientID that doesn't match, check acl should return false", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, topic, "fake_client_id", MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
|
@ -638,7 +746,7 @@ func TestJWTJsonStatusOnlyServer(t *testing.T) {
|
|||
|
||||
topic := "test/topic"
|
||||
var acc = int64(1)
|
||||
clientId := "test_client"
|
||||
clientID := "test_client"
|
||||
token, _ := jwtToken.SignedString([]byte(jwtSecret))
|
||||
wrongToken, _ := wrongJwtToken.SignedString([]byte(jwtSecret))
|
||||
|
||||
|
@ -670,7 +778,7 @@ func TestJWTJsonStatusOnlyServer(t *testing.T) {
|
|||
case "/acl":
|
||||
params = data.(map[string]interface{})
|
||||
paramsAcc := int64(params["acc"].(float64))
|
||||
if params["topic"].(string) == topic && params["clientid"].(string) == clientId && paramsAcc <= acc {
|
||||
if params["topic"].(string) == topic && params["clientid"].(string) == clientID && paramsAcc <= acc {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
break
|
||||
}
|
||||
|
@ -682,7 +790,7 @@ func TestJWTJsonStatusOnlyServer(t *testing.T) {
|
|||
defer mockServer.Close()
|
||||
|
||||
authOpts := make(map[string]string)
|
||||
authOpts["jwt_remote"] = "true"
|
||||
authOpts["jwt_mode"] = "remote"
|
||||
authOpts["jwt_params_mode"] = "json"
|
||||
authOpts["jwt_response_mode"] = "status"
|
||||
authOpts["jwt_host"] = strings.Replace(mockServer.URL, "http://", "", -1)
|
||||
|
@ -718,7 +826,10 @@ func TestJWTJsonStatusOnlyServer(t *testing.T) {
|
|||
So(authenticated, ShouldBeTrue)
|
||||
|
||||
Convey("But disabling superusers by removing superuri should now return false", func() {
|
||||
hb.SuperuserUri = ""
|
||||
authOpts["jwt_superuser_uri"] = ""
|
||||
hb, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
superuser, err := hb.GetSuperuser(username)
|
||||
So(err, ShouldBeNil)
|
||||
So(superuser, ShouldBeFalse)
|
||||
|
@ -736,7 +847,7 @@ func TestJWTJsonStatusOnlyServer(t *testing.T) {
|
|||
|
||||
Convey("Given correct topic, username, client id and acc, acl check should return true", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientId, MOSQ_ACL_READ)
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeTrue)
|
||||
|
||||
|
@ -744,7 +855,7 @@ func TestJWTJsonStatusOnlyServer(t *testing.T) {
|
|||
|
||||
Convey("Given an acc that requires more privileges than the user has, check acl should return false", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientId, MOSQ_ACL_WRITE)
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_WRITE)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeFalse)
|
||||
|
||||
|
@ -752,13 +863,13 @@ func TestJWTJsonStatusOnlyServer(t *testing.T) {
|
|||
|
||||
Convey("Given a topic not present in acls, check acl should return false", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, "fake/topic", clientId, MOSQ_ACL_READ)
|
||||
authenticated, err := hb.CheckAcl(token, "fake/topic", clientID, MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeFalse)
|
||||
|
||||
})
|
||||
|
||||
Convey("Given a clientId that doesn't match, check acl should return false", func() {
|
||||
Convey("Given a clientID that doesn't match, check acl should return false", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, topic, "fake_client_id", MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
|
@ -776,7 +887,7 @@ func TestJWTJsonTextResponseServer(t *testing.T) {
|
|||
|
||||
topic := "test/topic"
|
||||
var acc = int64(1)
|
||||
clientId := "test_client"
|
||||
clientID := "test_client"
|
||||
token, _ := jwtToken.SignedString([]byte(jwtSecret))
|
||||
wrongToken, _ := wrongJwtToken.SignedString([]byte(jwtSecret))
|
||||
|
||||
|
@ -810,7 +921,7 @@ func TestJWTJsonTextResponseServer(t *testing.T) {
|
|||
case "/acl":
|
||||
params = data.(map[string]interface{})
|
||||
paramsAcc := int64(params["acc"].(float64))
|
||||
if params["topic"].(string) == topic && params["clientid"].(string) == clientId && paramsAcc <= acc {
|
||||
if params["topic"].(string) == topic && params["clientid"].(string) == clientID && paramsAcc <= acc {
|
||||
w.Write([]byte("ok"))
|
||||
break
|
||||
}
|
||||
|
@ -822,7 +933,7 @@ func TestJWTJsonTextResponseServer(t *testing.T) {
|
|||
defer mockServer.Close()
|
||||
|
||||
authOpts := make(map[string]string)
|
||||
authOpts["jwt_remote"] = "true"
|
||||
authOpts["jwt_mode"] = "remote"
|
||||
authOpts["jwt_params_mode"] = "json"
|
||||
authOpts["jwt_response_mode"] = "text"
|
||||
authOpts["jwt_host"] = strings.Replace(mockServer.URL, "http://", "", -1)
|
||||
|
@ -858,7 +969,10 @@ func TestJWTJsonTextResponseServer(t *testing.T) {
|
|||
So(authenticated, ShouldBeTrue)
|
||||
|
||||
Convey("But disabling superusers by removing superuri should now return false", func() {
|
||||
hb.SuperuserUri = ""
|
||||
authOpts["jwt_superuser_uri"] = ""
|
||||
hb, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
superuser, err := hb.GetSuperuser(username)
|
||||
So(err, ShouldBeNil)
|
||||
So(superuser, ShouldBeFalse)
|
||||
|
@ -876,7 +990,7 @@ func TestJWTJsonTextResponseServer(t *testing.T) {
|
|||
|
||||
Convey("Given correct topic, username, client id and acc, acl check should return true", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientId, MOSQ_ACL_READ)
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeTrue)
|
||||
|
||||
|
@ -884,7 +998,7 @@ func TestJWTJsonTextResponseServer(t *testing.T) {
|
|||
|
||||
Convey("Given an acc that requires more privileges than the user has, check acl should return false", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientId, MOSQ_ACL_WRITE)
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_WRITE)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeFalse)
|
||||
|
||||
|
@ -892,13 +1006,13 @@ func TestJWTJsonTextResponseServer(t *testing.T) {
|
|||
|
||||
Convey("Given a topic not present in acls, check acl should return false", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, "fake/topic", clientId, MOSQ_ACL_READ)
|
||||
authenticated, err := hb.CheckAcl(token, "fake/topic", clientID, MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeFalse)
|
||||
|
||||
})
|
||||
|
||||
Convey("Given a clientId that doesn't match, check acl should return false", func() {
|
||||
Convey("Given a clientID that doesn't match, check acl should return false", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, topic, "fake_client_id", MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
|
@ -916,7 +1030,7 @@ func TestJWTFormJsonResponseServer(t *testing.T) {
|
|||
|
||||
topic := "test/topic"
|
||||
var acc = int64(1)
|
||||
clientId := "test_client"
|
||||
clientID := "test_client"
|
||||
token, _ := jwtToken.SignedString([]byte(jwtSecret))
|
||||
wrongToken, _ := wrongJwtToken.SignedString([]byte(jwtSecret))
|
||||
|
||||
|
@ -950,7 +1064,7 @@ func TestJWTFormJsonResponseServer(t *testing.T) {
|
|||
httpResponse.Error = ""
|
||||
case "/acl":
|
||||
paramsAcc, _ := strconv.ParseInt(params["acc"][0], 10, 64)
|
||||
if params["topic"][0] == topic && params["clientid"][0] == clientId && paramsAcc <= acc {
|
||||
if params["topic"][0] == topic && params["clientid"][0] == clientID && paramsAcc <= acc {
|
||||
httpResponse.Ok = true
|
||||
httpResponse.Error = ""
|
||||
break
|
||||
|
@ -972,7 +1086,7 @@ func TestJWTFormJsonResponseServer(t *testing.T) {
|
|||
defer mockServer.Close()
|
||||
|
||||
authOpts := make(map[string]string)
|
||||
authOpts["jwt_remote"] = "true"
|
||||
authOpts["jwt_mode"] = "remote"
|
||||
authOpts["jwt_params_mode"] = "form"
|
||||
authOpts["jwt_response_mode"] = "json"
|
||||
authOpts["jwt_host"] = strings.Replace(mockServer.URL, "http://", "", -1)
|
||||
|
@ -1008,7 +1122,10 @@ func TestJWTFormJsonResponseServer(t *testing.T) {
|
|||
So(authenticated, ShouldBeTrue)
|
||||
|
||||
Convey("But disabling superusers by removing superuri should now return false", func() {
|
||||
hb.SuperuserUri = ""
|
||||
authOpts["jwt_superuser_uri"] = ""
|
||||
hb, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
superuser, err := hb.GetSuperuser(username)
|
||||
So(err, ShouldBeNil)
|
||||
So(superuser, ShouldBeFalse)
|
||||
|
@ -1026,7 +1143,7 @@ func TestJWTFormJsonResponseServer(t *testing.T) {
|
|||
|
||||
Convey("Given correct topic, username, client id and acc, acl check should return true", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientId, MOSQ_ACL_READ)
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeTrue)
|
||||
|
||||
|
@ -1034,7 +1151,7 @@ func TestJWTFormJsonResponseServer(t *testing.T) {
|
|||
|
||||
Convey("Given an acc that requires more privileges than the user has, check acl should return false", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientId, MOSQ_ACL_WRITE)
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_WRITE)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeFalse)
|
||||
|
||||
|
@ -1042,13 +1159,13 @@ func TestJWTFormJsonResponseServer(t *testing.T) {
|
|||
|
||||
Convey("Given a topic not present in acls, check acl should return false", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, "fake/topic", clientId, MOSQ_ACL_READ)
|
||||
authenticated, err := hb.CheckAcl(token, "fake/topic", clientID, MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeFalse)
|
||||
|
||||
})
|
||||
|
||||
Convey("Given a clientId that doesn't match, check acl should return false", func() {
|
||||
Convey("Given a clientID that doesn't match, check acl should return false", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, topic, "fake_client_id", MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
|
@ -1066,7 +1183,7 @@ func TestJWTFormStatusOnlyServer(t *testing.T) {
|
|||
|
||||
topic := "test/topic"
|
||||
var acc = int64(1)
|
||||
clientId := "test_client"
|
||||
clientID := "test_client"
|
||||
token, _ := jwtToken.SignedString([]byte(jwtSecret))
|
||||
wrongToken, _ := wrongJwtToken.SignedString([]byte(jwtSecret))
|
||||
|
||||
|
@ -1092,7 +1209,7 @@ func TestJWTFormStatusOnlyServer(t *testing.T) {
|
|||
w.WriteHeader(http.StatusOK)
|
||||
case "/acl":
|
||||
paramsAcc, _ := strconv.ParseInt(params["acc"][0], 10, 64)
|
||||
if params["topic"][0] == topic && params["clientid"][0] == clientId && paramsAcc <= acc {
|
||||
if params["topic"][0] == topic && params["clientid"][0] == clientID && paramsAcc <= acc {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
break
|
||||
}
|
||||
|
@ -1104,7 +1221,7 @@ func TestJWTFormStatusOnlyServer(t *testing.T) {
|
|||
defer mockServer.Close()
|
||||
|
||||
authOpts := make(map[string]string)
|
||||
authOpts["jwt_remote"] = "true"
|
||||
authOpts["jwt_mode"] = "remote"
|
||||
authOpts["jwt_params_mode"] = "form"
|
||||
authOpts["jwt_response_mode"] = "status"
|
||||
authOpts["jwt_host"] = strings.Replace(mockServer.URL, "http://", "", -1)
|
||||
|
@ -1140,7 +1257,10 @@ func TestJWTFormStatusOnlyServer(t *testing.T) {
|
|||
So(authenticated, ShouldBeTrue)
|
||||
|
||||
Convey("But disabling superusers by removing superuri should now return false", func() {
|
||||
hb.SuperuserUri = ""
|
||||
authOpts["jwt_superuser_uri"] = ""
|
||||
hb, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
superuser, err := hb.GetSuperuser(username)
|
||||
So(err, ShouldBeNil)
|
||||
So(superuser, ShouldBeFalse)
|
||||
|
@ -1158,7 +1278,7 @@ func TestJWTFormStatusOnlyServer(t *testing.T) {
|
|||
|
||||
Convey("Given correct topic, username, client id and acc, acl check should return true", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientId, MOSQ_ACL_READ)
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeTrue)
|
||||
|
||||
|
@ -1166,7 +1286,7 @@ func TestJWTFormStatusOnlyServer(t *testing.T) {
|
|||
|
||||
Convey("Given an acc that requires more privileges than the user has, check acl should return false", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientId, MOSQ_ACL_WRITE)
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_WRITE)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeFalse)
|
||||
|
||||
|
@ -1174,13 +1294,13 @@ func TestJWTFormStatusOnlyServer(t *testing.T) {
|
|||
|
||||
Convey("Given a topic not present in acls, check acl should return false", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, "fake/topic", clientId, MOSQ_ACL_READ)
|
||||
authenticated, err := hb.CheckAcl(token, "fake/topic", clientID, MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeFalse)
|
||||
|
||||
})
|
||||
|
||||
Convey("Given a clientId that doesn't match, check acl should return false", func() {
|
||||
Convey("Given a clientID that doesn't match, check acl should return false", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, topic, "fake_client_id", MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
|
@ -1198,7 +1318,7 @@ func TestJWTFormTextResponseServer(t *testing.T) {
|
|||
|
||||
topic := "test/topic"
|
||||
var acc = int64(1)
|
||||
clientId := "test_client"
|
||||
clientID := "test_client"
|
||||
token, _ := jwtToken.SignedString([]byte(jwtSecret))
|
||||
wrongToken, _ := wrongJwtToken.SignedString([]byte(jwtSecret))
|
||||
|
||||
|
@ -1227,7 +1347,7 @@ func TestJWTFormTextResponseServer(t *testing.T) {
|
|||
w.Write([]byte("ok"))
|
||||
case "/acl":
|
||||
paramsAcc, _ := strconv.ParseInt(params["acc"][0], 10, 64)
|
||||
if params["topic"][0] == topic && params["clientid"][0] == clientId && paramsAcc <= acc {
|
||||
if params["topic"][0] == topic && params["clientid"][0] == clientID && paramsAcc <= acc {
|
||||
w.Write([]byte("ok"))
|
||||
break
|
||||
}
|
||||
|
@ -1239,7 +1359,7 @@ func TestJWTFormTextResponseServer(t *testing.T) {
|
|||
defer mockServer.Close()
|
||||
|
||||
authOpts := make(map[string]string)
|
||||
authOpts["jwt_remote"] = "true"
|
||||
authOpts["jwt_mode"] = "remote"
|
||||
authOpts["jwt_params_mode"] = "form"
|
||||
authOpts["jwt_response_mode"] = "text"
|
||||
authOpts["jwt_host"] = strings.Replace(mockServer.URL, "http://", "", -1)
|
||||
|
@ -1275,7 +1395,10 @@ func TestJWTFormTextResponseServer(t *testing.T) {
|
|||
So(authenticated, ShouldBeTrue)
|
||||
|
||||
Convey("But disabling superusers by removing superuri should now return false", func() {
|
||||
hb.SuperuserUri = ""
|
||||
authOpts["jwt_superuser_uri"] = ""
|
||||
hb, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
superuser, err := hb.GetSuperuser(username)
|
||||
So(err, ShouldBeNil)
|
||||
So(superuser, ShouldBeFalse)
|
||||
|
@ -1293,7 +1416,7 @@ func TestJWTFormTextResponseServer(t *testing.T) {
|
|||
|
||||
Convey("Given correct topic, username, client id and acc, acl check should return true", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientId, MOSQ_ACL_READ)
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeTrue)
|
||||
|
||||
|
@ -1301,7 +1424,7 @@ func TestJWTFormTextResponseServer(t *testing.T) {
|
|||
|
||||
Convey("Given an acc that requires more privileges than the user has, check acl should return false", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientId, MOSQ_ACL_WRITE)
|
||||
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_WRITE)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeFalse)
|
||||
|
||||
|
@ -1309,13 +1432,13 @@ func TestJWTFormTextResponseServer(t *testing.T) {
|
|||
|
||||
Convey("Given a topic not present in acls, check acl should return false", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, "fake/topic", clientId, MOSQ_ACL_READ)
|
||||
authenticated, err := hb.CheckAcl(token, "fake/topic", clientID, MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
So(authenticated, ShouldBeFalse)
|
||||
|
||||
})
|
||||
|
||||
Convey("Given a clientId that doesn't match, check acl should return false", func() {
|
||||
Convey("Given a clientID that doesn't match, check acl should return false", func() {
|
||||
|
||||
authenticated, err := hb.CheckAcl(token, topic, "fake_client_id", MOSQ_ACL_READ)
|
||||
So(err, ShouldBeNil)
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"database/sql"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
mq "github.com/go-sql-driver/mysql"
|
||||
|
@ -34,6 +35,8 @@ type Mysql struct {
|
|||
SocketPath string
|
||||
AllowNativePasswords bool
|
||||
hasher hashing.HashComparer
|
||||
|
||||
connectTries int
|
||||
}
|
||||
|
||||
func NewMysql(authOpts map[string]string, logLevel log.Level, hasher hashing.HashComparer) (Mysql, error) {
|
||||
|
@ -190,8 +193,18 @@ func NewMysql(authOpts map[string]string, logLevel log.Level, hasher hashing.Has
|
|||
}
|
||||
}
|
||||
|
||||
if tries, ok := authOpts["mysql_connect_tries"]; ok {
|
||||
connectTries, err := strconv.Atoi(tries)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("invalid mysql connect tries options: %s", err)
|
||||
} else {
|
||||
mysql.connectTries = connectTries
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
mysql.DB, err = OpenDatabase(msConfig.FormatDSN(), "mysql")
|
||||
mysql.DB, err = OpenDatabase(msConfig.FormatDSN(), "mysql", mysql.connectTries)
|
||||
|
||||
if err != nil {
|
||||
return mysql, errors.Errorf("MySql backend error: couldn't open db: %s", err)
|
||||
|
|
|
@ -3,6 +3,7 @@ package backends
|
|||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/iegomez/mosquitto-go-auth/hashing"
|
||||
|
@ -28,6 +29,8 @@ type Postgres struct {
|
|||
SSLKey string
|
||||
SSLRootCert string
|
||||
hasher hashing.HashComparer
|
||||
|
||||
connectTries int
|
||||
}
|
||||
|
||||
func NewPostgres(authOpts map[string]string, logLevel log.Level, hasher hashing.HashComparer) (Postgres, error) {
|
||||
|
@ -46,6 +49,7 @@ func NewPostgres(authOpts map[string]string, logLevel log.Level, hasher hashing.
|
|||
SuperuserQuery: "",
|
||||
AclQuery: "",
|
||||
hasher: hasher,
|
||||
connectTries: -1,
|
||||
}
|
||||
|
||||
if host, ok := authOpts["pg_host"]; ok {
|
||||
|
@ -128,14 +132,24 @@ func NewPostgres(authOpts map[string]string, logLevel log.Level, hasher hashing.
|
|||
|
||||
if (postgres.SSLMode == "verify-ca" || postgres.SSLMode == "verify-full") && checkSSL {
|
||||
connStr = fmt.Sprintf("%s sslmode=verify-ca sslcert=%s sslkey=%s sslrootcert=%s", connStr, postgres.SSLCert, postgres.SSLKey, postgres.SSLRootCert)
|
||||
} else if postgres.SSLMode == "required" {
|
||||
} else if postgres.SSLMode == "require" {
|
||||
connStr = fmt.Sprintf("%s sslmode=require", connStr)
|
||||
} else {
|
||||
connStr = fmt.Sprintf("%s sslmode=disable", connStr)
|
||||
}
|
||||
|
||||
if tries, ok := authOpts["pg_connect_tries"]; ok {
|
||||
connectTries, err := strconv.Atoi(tries)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("invalid postgres connect tries options: %s", err)
|
||||
} else {
|
||||
postgres.connectTries = connectTries
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
postgres.DB, err = OpenDatabase(connStr, "postgres")
|
||||
postgres.DB, err = OpenDatabase(connStr, "postgres", postgres.connectTries)
|
||||
|
||||
if err != nil {
|
||||
return postgres, errors.Errorf("PG backend error: couldn't open db: %s", err)
|
||||
|
|
|
@ -2,6 +2,7 @@ package backends
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/iegomez/mosquitto-go-auth/hashing"
|
||||
|
@ -19,6 +20,8 @@ type Sqlite struct {
|
|||
SuperuserQuery string
|
||||
AclQuery string
|
||||
hasher hashing.HashComparer
|
||||
|
||||
connectTries int
|
||||
}
|
||||
|
||||
func NewSqlite(authOpts map[string]string, logLevel log.Level, hasher hashing.HashComparer) (Sqlite, error) {
|
||||
|
@ -69,8 +72,18 @@ func NewSqlite(authOpts map[string]string, logLevel log.Level, hasher hashing.Ha
|
|||
connStr = sqlite.Source
|
||||
}
|
||||
|
||||
if tries, ok := authOpts["sqlite_connect_tries"]; ok {
|
||||
connectTries, err := strconv.Atoi(tries)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("invalid sqlite connect tries options: %s", err)
|
||||
} else {
|
||||
sqlite.connectTries = connectTries
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
sqlite.DB, err = OpenDatabase(connStr, "sqlite3")
|
||||
sqlite.DB, err = OpenDatabase(connStr, "sqlite3", sqlite.connectTries)
|
||||
|
||||
if err != nil {
|
||||
return sqlite, errors.Errorf("sqlite backend error: couldn't open db %s: %s", connStr, err)
|
||||
|
|
|
@ -247,7 +247,7 @@ func (s *goStore) SetAuthRecord(ctx context.Context, username, password string,
|
|||
//SetAclCache sets a mix, granted option and expiration time.
|
||||
func (s *goStore) SetACLRecord(ctx context.Context, username, topic, clientid string, acc int, granted string) error {
|
||||
record := toACLRecord(username, topic, clientid, acc, s.h)
|
||||
s.client.Set(record, granted, s.authExpiration)
|
||||
s.client.Set(record, granted, s.aclExpiration)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -261,7 +261,7 @@ func (s *redisStore) SetAuthRecord(ctx context.Context, username, password strin
|
|||
//SetAclCache sets a mix, granted option and expiration time.
|
||||
func (s *redisStore) SetACLRecord(ctx context.Context, username, topic, clientid string, acc int, granted string) error {
|
||||
record := toACLRecord(username, topic, clientid, acc, s.h)
|
||||
return s.setRecord(ctx, record, granted, s.authExpiration)
|
||||
return s.setRecord(ctx, record, granted, s.aclExpiration)
|
||||
}
|
||||
|
||||
func (s *redisStore) setRecord(ctx context.Context, record, granted string, expirationTime time.Duration) error {
|
||||
|
|
|
@ -21,8 +21,12 @@ RUN tar xzvf mosquitto-${MOSQUITTO_VERSION}.tar.gz && rm mosquitto-${MOSQUITTO_V
|
|||
RUN cd mosquitto-${MOSQUITTO_VERSION} && make WITH_WEBSOCKETS=yes && make install && cd ..
|
||||
|
||||
#Get Go.
|
||||
RUN wget https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz && tar -C /usr/local -xzf go${GO_VERSION}.linux-amd64.tar.gz
|
||||
RUN export PATH=$PATH:/usr/local/go/bin && go version && rm go${GO_VERSION}.linux-amd64.tar.gz
|
||||
RUN export GO_ARCH=$(uname -m | sed -es/x86_64/amd64/ -es/armv7l/armv6l/ -es/aarch64/arm64/) && \
|
||||
wget https://dl.google.com/go/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz && \
|
||||
tar -C /usr/local -xzf go${GO_VERSION}.linux-${GO_ARCH}.tar.gz && \
|
||||
export PATH=$PATH:/usr/local/go/bin && \
|
||||
go version && \
|
||||
rm go${GO_VERSION}.linux-${GO_ARCH}.tar.gz
|
||||
|
||||
#Get / build the plugin.
|
||||
RUN mkdir mosquitto-go-auth && \
|
||||
|
|
17
go-auth.go
17
go-auth.go
|
@ -10,10 +10,9 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/iegomez/mosquitto-go-auth/hashing"
|
||||
|
||||
bes "github.com/iegomez/mosquitto-go-auth/backends"
|
||||
"github.com/iegomez/mosquitto-go-auth/cache"
|
||||
"github.com/iegomez/mosquitto-go-auth/hashing"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
@ -58,6 +57,7 @@ const (
|
|||
mongoBackend = "mongo"
|
||||
pluginBackend = "plugin"
|
||||
grpcBackend = "grpc"
|
||||
jsBackend = "js"
|
||||
|
||||
AuthRejected = 0
|
||||
AuthGranted = 1
|
||||
|
@ -76,6 +76,7 @@ var allowedBackendsOptsPrefix = map[string]string{
|
|||
mongoBackend: "mongo",
|
||||
pluginBackend: "plugin",
|
||||
grpcBackend: "grpc",
|
||||
jsBackend: "js",
|
||||
}
|
||||
|
||||
var backends []string //List of selected backends.
|
||||
|
@ -292,7 +293,7 @@ func AuthPluginInit(keys []string, values []string, authOptsNum int) {
|
|||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
cmBackends[jwtBackend] = beIface.(bes.JWT)
|
||||
cmBackends[jwtBackend] = beIface.(*bes.JWT)
|
||||
}
|
||||
case filesBackend:
|
||||
beIface, err = bes.NewFiles(authOpts, authPlugin.logLevel, hasher)
|
||||
|
@ -350,6 +351,14 @@ func AuthPluginInit(keys []string, values []string, authOptsNum int) {
|
|||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
cmBackends[grpcBackend] = beIface.(bes.GRPC)
|
||||
}
|
||||
case jsBackend:
|
||||
beIface, err = bes.NewJavascript(authOpts, authPlugin.logLevel)
|
||||
if err != nil {
|
||||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
cmBackends[jsBackend] = beIface.(*bes.Javascript)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -421,7 +430,7 @@ func setCache(authOpts map[string]string) {
|
|||
}
|
||||
|
||||
refreshExpiration := false
|
||||
if refresh, ok := authOpts["cache_rfresh"]; ok && refresh == "true" {
|
||||
if refresh, ok := authOpts["cache_refresh"]; ok && refresh == "true" {
|
||||
refreshExpiration = true
|
||||
}
|
||||
|
||||
|
|
15
go.mod
15
go.mod
|
@ -3,37 +3,30 @@ module github.com/iegomez/mosquitto-go-auth
|
|||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/brocaar/lora-app-server v2.5.1+incompatible
|
||||
github.com/brocaar/loraserver v2.5.0+incompatible // indirect
|
||||
github.com/brocaar/lorawan v0.0.0-20190523144945-4c051b1fa597 // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/eclipse/paho.mqtt.golang v1.2.0 // indirect
|
||||
github.com/go-redis/redis v6.15.8+incompatible
|
||||
github.com/go-redis/redis/v8 v8.0.0-beta.2
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/golang/protobuf v1.4.2
|
||||
github.com/gomodule/redigo v2.0.0+incompatible // indirect
|
||||
github.com/googleapis/gax-go v2.0.2+incompatible // indirect
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190328170749-bb2674552d8f // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0 // indirect
|
||||
github.com/jmoiron/sqlx v1.2.0
|
||||
github.com/klauspost/compress v1.10.6 // indirect
|
||||
github.com/lib/pq v1.5.2
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3 // indirect
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/xdg/stringprep v1.0.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.3.3
|
||||
go.opencensus.io v0.22.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 // indirect
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a // indirect
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 // indirect
|
||||
google.golang.org/api v0.6.0 // indirect
|
||||
google.golang.org/appengine v1.6.5
|
||||
google.golang.org/genproto v0.0.0-20200521103424-e9a78aa275b7 // indirect
|
||||
google.golang.org/grpc v1.29.1
|
||||
gopkg.in/sourcemap.v1 v1.0.5 // indirect
|
||||
)
|
||||
|
|
155
go.sum
155
go.sum
|
@ -1,18 +1,10 @@
|
|||
cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7 h1:qELHH0AWCvf98Yf+CNIJx9vOZOfHFDDzgDRYsnNk/vs=
|
||||
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60=
|
||||
github.com/NickBall/go-aes-key-wrap v0.0.0-20170929221519-1c3aa3e4dfc5/go.mod h1:w5D10RxC0NmPYxmQ438CC1S07zaC1zpvuNW7s5sUk2Q=
|
||||
github.com/benbjohnson/clock v1.0.0 h1:78Jk/r6m4wCi6sndMpty7A//t4dw/RW5fV4ZgDVfX1w=
|
||||
github.com/benbjohnson/clock v1.0.0/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
|
||||
github.com/brocaar/lora-app-server v2.5.1+incompatible h1:F//0TncqDS9uKC4yTrJTTnlwfvM9Ie/KgRDSgWPA6as=
|
||||
github.com/brocaar/lora-app-server v2.5.1+incompatible/go.mod h1:Thw3wBnUbdwaTporobKVwffFSfHvdrjpOSIvbaO2YMU=
|
||||
github.com/brocaar/loraserver v2.5.0+incompatible h1:Fna4CF0jW2Vl4UpjLIhR5ifW4g+oZD/w3Dq09TiJ8Z8=
|
||||
github.com/brocaar/loraserver v2.5.0+incompatible/go.mod h1:VBTim0YtfWAKehjJ6k17jCnG44DzXVdL4iu+hwxg2ik=
|
||||
github.com/brocaar/lorawan v0.0.0-20190523144945-4c051b1fa597 h1:bYzV3+MYStooVxZwloCHvOUDsFjTKS8vdRJ9jZkEd/s=
|
||||
github.com/brocaar/lorawan v0.0.0-20190523144945-4c051b1fa597/go.mod h1:Fm+51pxK6mZoAQjIaWJqPmnRuXecozsM5Mf9c+kr/ko=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
|
@ -21,24 +13,14 @@ 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/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/eclipse/paho.mqtt.golang v1.2.0 h1:1F8mhG9+aO5/xpdtFkW4SxOJB67ukuDC3t2y2qayIX0=
|
||||
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-redis/redis v6.14.1+incompatible h1:kSJohAREGMr344uMa8PzuIg5OU6ylCbyDkWkkNOfEik=
|
||||
github.com/go-redis/redis v6.14.1+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-redis/redis v6.15.7+incompatible h1:3skhDh95XQMpnqeqNftPkQD9jL9e5e36z/1SUm6dy1U=
|
||||
github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-redis/redis v6.15.8+incompatible h1:BKZuG6mCnRj5AOaWJXoCgf6rqTYnYJLe4en2hxT7r9o=
|
||||
github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-redis/redis/v8 v8.0.0-beta.2 h1:9S28J9QMBotgI3tGgXbX1Wk9i8QYC3Orw4bTLoPrQeI=
|
||||
github.com/go-redis/redis/v8 v8.0.0-beta.2/go.mod h1:o1M7JtsgfDYyv3o+gBn/jJ1LkqpnCrmil7PSppZGBak=
|
||||
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
|
||||
|
@ -71,15 +53,13 @@ github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWe
|
|||
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
|
||||
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
|
||||
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
|
||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
|
@ -93,49 +73,25 @@ github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0
|
|||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
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/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww=
|
||||
github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4 h1:hU4mGcQI4DaAYW+IbTun+2qEZVFxK0ySjQLTbS0VQKc=
|
||||
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 h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190328170749-bb2674552d8f h1:4Gslotqbs16iAg+1KR/XdabIfq8TlAWHdwS5QJFksLc=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190328170749-bb2674552d8f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 h1:0IKlLyQ3Hs9nDaiK5cSHAGmcQEIC8l2Ts1u6x5Dfrqg=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jacobsa/crypto v0.0.0-20180924003735-d95898ceee07 h1:/PaS1RNKtbBEndIvzCqIgYh6GAH9ZFc8Mj4tVRVyfOA=
|
||||
github.com/jacobsa/crypto v0.0.0-20180924003735-d95898ceee07/go.mod h1:LadVJg0XuawGk+8L1rYnIED8451UyNxEMdTWCEt5kmU=
|
||||
github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd/go.mod h1:TlmyIZDpGmwRoTWiakdr+HA1Tukze6C6XbRVidYq02M=
|
||||
github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff/go.mod h1:gJWba/XXGl0UoOmBQKRWCJdHrr3nE0T65t6ioaj3mLI=
|
||||
github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11/go.mod h1:+DBdDyfoO2McrOyDemRBq0q9CMEByef7sYl7JH5Q3BI=
|
||||
github.com/jacobsa/reqtrace v0.0.0-20150505043853-245c9e0234cb/go.mod h1:ivcmUvxXWjb27NsPEaiYK7AidlZXS7oQ5PowUS9z3I4=
|
||||
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0 h1:5B0uxl2lzNRVkJVg+uGHxWtRt4C0Wjc6kJKo5XYx8xE=
|
||||
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
|
||||
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
|
||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
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/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
|
||||
|
@ -145,12 +101,8 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL
|
|||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M=
|
||||
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.10.1 h1:a/QY0o9S6wCi0XhxaMX/QmusicNUqCqFugR6WKPOSoQ=
|
||||
github.com/klauspost/compress v1.10.1/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.10.6 h1:SP6zavvTG3YjOosWePXFDlExpKIWMTO4SE/Y8MZB2vI=
|
||||
github.com/klauspost/compress v1.10.6/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs=
|
||||
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
|
@ -158,13 +110,13 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxv
|
|||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/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 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||
github.com/lib/pq v1.0.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/lib/pq v1.5.2 h1:yTSXVswvWUOQ3k1sd7vJfDrbSl8lKuscqFJRqjC0ifw=
|
||||
github.com/lib/pq v1.5.2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
|
||||
|
@ -175,15 +127,12 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJK
|
|||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
|
||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
|
||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/patrickmn/go-cache v1.0.0 h1:3gD5McaYs9CxjyK5AXGcq8gdeCARtd/9gJDUvVeaZ0Y=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
|
||||
|
@ -196,14 +145,11 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac h1:kYPjbEN6YPYWWHI6ky1J813KzIq/8+Wg4TO4xU7A/KU=
|
||||
github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/sirupsen/logrus v1.1.0 h1:65VZabgUiV9ktjGM5nTq0+YurgTyX+YI2lSSfDjI+qU=
|
||||
github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A=
|
||||
github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME=
|
||||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
|
@ -211,12 +157,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
|
|||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac h1:wbW+Bybf9pXxnCFAOWZTqkRjAc7rAIwo2e1ArUhiHxg=
|
||||
github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3 h1:hBSHahWMEgzwRyS6dRpxY0XyjZsHyQ61s084wo5PJe0=
|
||||
github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff h1:86HlEv0yBCry9syNuylzqznKXDK11p6D0DT596yNMys=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
|
@ -231,155 +173,93 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
|||
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=
|
||||
github.com/tidwall/pretty v0.0.0-20190325153808-1166b9ac2b65 h1:rQ229MBgvW68s1/g6f1/63TgYwYxfF4E+bi/KC19P8g=
|
||||
github.com/tidwall/pretty v0.0.0-20190325153808-1166b9ac2b65/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk=
|
||||
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
|
||||
github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
|
||||
github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0=
|
||||
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
|
||||
go.mongodb.org/mongo-driver v1.0.0 h1:KxPRDyfB2xXnDE2My8acoOWBQkfv3tz0SaWTRZjJR0c=
|
||||
go.mongodb.org/mongo-driver v1.0.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.mongodb.org/mongo-driver v1.3.0 h1:ew6uUIeJOo+qdUUv7LxFCUhtWmVv7ZV/Xuy4FAUsw2E=
|
||||
go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE=
|
||||
go.mongodb.org/mongo-driver v1.3.3 h1:9kX7WY6sU/5qBuhm5mdnNWdqaDAQKB2qSZOd5wMEPGQ=
|
||||
go.mongodb.org/mongo-driver v1.3.3/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opentelemetry.io/otel v0.5.0 h1:tdIR1veg/z+VRJaw/6SIxz+QX3l+m+BDleYLTs+GC1g=
|
||||
go.opentelemetry.io/otel v0.5.0/go.mod h1:jzBIgIzK43Iu1BpDAXwqOd6UPsSAk+ewVZ5ofSXw4Ek=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4 h1:Vk3wNqEZwyGyei9yq5ekj7frek2u7HUfffJ1/opblzc=
|
||||
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200219183655-46282727080f h1:dB42wwhNuwPvh8f+5zZWNcU+F2Xs/B9wXXwvUCOH7r8=
|
||||
golang.org/x/net v0.0.0-20200219183655-46282727080f/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 h1:eDrdRpKgkcCqKZQwyZRyeFZgfqt37SL7Kv3tok06cKE=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181003145944-af653ce8b74f h1:zAtpFwFDtnvBWPPelq8CSiqRN1wrIzMUk9dwzbpjpNM=
|
||||
golang.org/x/sys v0.0.0-20181003145944-af653ce8b74f/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190402054613-e4093980e83e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c h1:jceGD5YNJGgGMkJz79agzOln1K9TaZUjv5ird16qniQ=
|
||||
golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.6.0 h1:2tJEkRfnZL5g1GeBUlITh/rqT5HG3sFcoVCUUxmgJ2g=
|
||||
google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0 h1:S0iUepdCWODXRvtE+gcRDd15L+k+k1AiHlMiMjefH24=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
||||
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=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200218151345-dad8c97a84f5 h1:jB9+PJSvu5tBfmJHy/OVapFdjDF3WvpkqRhxqrmzoEU=
|
||||
google.golang.org/genproto v0.0.0-20200218151345-dad8c97a84f5/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200521103424-e9a78aa275b7 h1:JUs1uIDQ46c7iI0QuMPzAHqXaSmqKF0f9freFMk2ivs=
|
||||
google.golang.org/genproto v0.0.0-20200521103424-e9a78aa275b7/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
|
@ -398,20 +278,19 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
|||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
|
||||
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/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-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
@ -21,7 +21,7 @@ const (
|
|||
|
||||
// hashers
|
||||
Pbkdf2Opt = "pbkdf2"
|
||||
Argon2IDOpt = "argon2ID"
|
||||
Argon2IDOpt = "argon2id"
|
||||
BcryptOpt = "bcrypt"
|
||||
|
||||
// defaults
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
function checkAcl(username, topic, clientid, acc) {
|
||||
if(username != "correct") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(topic != "test/topic") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(clientid != "id") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(acc != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
checkAcl(username, topic, clientid, acc);
|
|
@ -0,0 +1,8 @@
|
|||
function checkSuperuser(username) {
|
||||
if(username == "admin") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
checkSuperuser(username);
|
|
@ -0,0 +1,8 @@
|
|||
function checkUser(username, password, clientid) {
|
||||
if(username == "correct" && password == "good") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
checkUser(username, password, clientid);
|
|
@ -0,0 +1,21 @@
|
|||
function checkAcl(token, topic, clientid, acc) {
|
||||
if(token != "correct") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(topic != "test/topic") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(clientid != "id") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(acc != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
checkAcl(token, topic, clientid, acc);
|
|
@ -0,0 +1,8 @@
|
|||
function checkUser(token, username) {
|
||||
if(username == "test") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
checkUser(token, username);
|
|
@ -0,0 +1,8 @@
|
|||
function checkSuperuser(token) {
|
||||
if(token == "admin") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
checkSuperuser(token);
|
|
@ -0,0 +1,8 @@
|
|||
function checkUser(token) {
|
||||
if(token == "correct") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
checkUser(token);
|
Loading…
Reference in New Issue