Merge branch 'master' into backend-error

This commit is contained in:
Pierre Fersing 2021-02-11 15:56:42 +01:00
commit 4be4a085d8
30 changed files with 1798 additions and 994 deletions

View File

@ -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 ./ ./

View File

@ -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

View File

@ -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
View File

@ -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

View File

@ -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

View File

@ -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

149
backends/javascript.go Normal file
View File

@ -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
}

View File

@ -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)
})
})
}

80
backends/js/runner.go Normal file
View File

@ -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
}

View File

@ -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
}

164
backends/jwt_javascript.go Normal file
View File

@ -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
}

185
backends/jwt_local.go Normal file
View File

@ -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
}

308
backends/jwt_remote.go Normal file
View File

@ -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
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

4
cache/cache.go vendored
View File

@ -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 {

View File

@ -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 && \

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -21,7 +21,7 @@ const (
// hashers
Pbkdf2Opt = "pbkdf2"
Argon2IDOpt = "argon2ID"
Argon2IDOpt = "argon2id"
BcryptOpt = "bcrypt"
// defaults

View File

@ -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);

View File

@ -0,0 +1,8 @@
function checkSuperuser(username) {
if(username == "admin") {
return true;
}
return false;
}
checkSuperuser(username);

View File

@ -0,0 +1,8 @@
function checkUser(username, password, clientid) {
if(username == "correct" && password == "good") {
return true;
}
return false;
}
checkUser(username, password, clientid);

View File

@ -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);

View File

@ -0,0 +1,8 @@
function checkUser(token, username) {
if(username == "test") {
return true;
}
return false;
}
checkUser(token, username);

View File

@ -0,0 +1,8 @@
function checkSuperuser(token) {
if(token == "admin") {
return true;
}
return false;
}
checkSuperuser(token);

View File

@ -0,0 +1,8 @@
function checkUser(token) {
if(token == "correct") {
return true;
}
return false;
}
checkUser(token);