diff --git a/AUTHELIA-V4.md b/AUTHELIA-V4.md index 6ba63615..dec4ed3d 100644 --- a/AUTHELIA-V4.md +++ b/AUTHELIA-V4.md @@ -8,7 +8,7 @@ that the system is more reliable overall. Majors changes: * The configuration mostly remained the same, only one major key has been added: `jwt_secret` and one key removed: `secure` from the SMTP notifier as the Go SMTP library default to TLS if available. -* The local storage previously used as a replacement of mongo for dev purpose was a `nedb` database which was implementing the same interface +* The local storage used for dev purpose was a `nedb` database which was implementing the same interface as mongo but was not really standard. It has been replaced by a good old sqlite3 database. * The model of the database is not compatible with v3. This has been decided to better fit with Golang libraries. * Some features have been upgraded such as U2F in order to use the latest security features available like allowing device cloning detection. @@ -32,9 +32,9 @@ for operations requiring identity validation. * Make sure users and groups filter in the LDAP configuration have outer parenthesis. The standard format of LDAP filters always include outer parenthesis. You can find some examples in the "Examples" section of the following document: https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx -#### If using the local storage +#### If you were using the local storage * Remove the directory of the storage (beware you will lose your previous configuration: U2F, TOTP devices). Replace the path with a path to a sqlite3 database, it is the new standard way of storing data in Authelia. -#### If using the mongo storage +#### If you were using the mongo storage * Flush your collections (beware you will lose your previous configuration: U2F, TOTP devices). New collections will be created by Authelia. diff --git a/BREAKING.md b/BREAKING.md index 6dee0e79..6cb28caa 100644 --- a/BREAKING.md +++ b/BREAKING.md @@ -8,9 +8,9 @@ Docker image but pick a version instead and check this file before upgrading. Th Authelia has been rewritten in Go for better performance and reliability. -### Model of U2F devices in MongoDB +### Model of U2F devices -The model of U2F devices stored in MongoDB has been updated to better fit with the Go library handling U2F keys. +The model of U2F devices has been updated to better fit with the Go library handling U2F keys. ### Removal of flag secure for SMTP notifier diff --git a/cmd/authelia-scripts/cmd_ci.go b/cmd/authelia-scripts/cmd_ci.go index dc5a64fe..e2d726db 100644 --- a/cmd/authelia-scripts/cmd_ci.go +++ b/cmd/authelia-scripts/cmd_ci.go @@ -7,7 +7,7 @@ import ( ) const dockerPullCommandLine = "docker-compose -f docker-compose.yml " + - "-f example/compose/mongo/docker-compose.yml " + + "-f example/compose/mariadb/docker-compose.yml " + "-f example/compose/redis/docker-compose.yml " + "-f example/compose/nginx/portal/docker-compose.yml " + "-f example/compose/smtp/docker-compose.yml " + diff --git a/cmd/authelia/main.go b/cmd/authelia/main.go index 22025cdc..f2d5cf94 100644 --- a/cmd/authelia/main.go +++ b/cmd/authelia/main.go @@ -6,17 +6,15 @@ import ( "log" "os" - "github.com/clems4ever/authelia/regulation" - - "github.com/clems4ever/authelia/session" - "github.com/clems4ever/authelia/authentication" "github.com/clems4ever/authelia/authorization" "github.com/clems4ever/authelia/configuration" "github.com/clems4ever/authelia/logging" "github.com/clems4ever/authelia/middlewares" "github.com/clems4ever/authelia/notification" + "github.com/clems4ever/authelia/regulation" "github.com/clems4ever/authelia/server" + "github.com/clems4ever/authelia/session" "github.com/clems4ever/authelia/storage" "github.com/sirupsen/logrus" ) @@ -70,8 +68,8 @@ func main() { } var storageProvider storage.Provider - if config.Storage.Mongo != nil { - storageProvider = storage.NewMongoProvider(*config.Storage.Mongo) + if config.Storage.SQL != nil { + storageProvider = storage.NewSQLProvider(*config.Storage.SQL) } else if config.Storage.Local != nil { storageProvider = storage.NewSQLiteProvider(config.Storage.Local.Path) } else { diff --git a/config.template.yml b/config.template.yml index 4301f577..b0877dfd 100644 --- a/config.template.yml +++ b/config.template.yml @@ -243,19 +243,19 @@ regulation: # Configuration of the storage backend used to store data and secrets. # -# You must use only an available configuration: local, mongo +# You must use only an available configuration: local, sql storage: # The directory where the DB files will be saved ## local: ## path: /var/lib/authelia/db.sqlite3 - # Settings to connect to mongo server - mongo: - url: mongodb://127.0.0.1 + # Settings to connect to SQL server + sql: + host: 127.0.0.1 + port: 3306 database: authelia - auth: - username: authelia - password: authelia + username: authelia + password: mypassword # Configuration of the notification system. # diff --git a/configuration/schema/storage.go b/configuration/schema/storage.go index 74c31fb5..0f63d5ef 100644 --- a/configuration/schema/storage.go +++ b/configuration/schema/storage.go @@ -1,22 +1,21 @@ package schema -// MongoStorageConfiguration represents the configuration related to mongo connection. -type MongoStorageConfiguration struct { - URL string `yaml:"url"` - Database string `yaml:"database"` - Auth struct { - Username string `yaml:"username"` - Password string `yaml:"password"` - } `yaml:"auth"` -} - // LocalStorageConfiguration represents the configuration when using local storage. type LocalStorageConfiguration struct { Path string `yaml:"path"` } +// SQLStorageConfiguration represents the configuration of the SQL database +type SQLStorageConfiguration struct { + Host string `yaml:"host"` + Port int `yaml:"port"` + Database string `yaml:"database"` + Username string `yaml:"username"` + Password string `yaml:"password"` +} + // StorageConfiguration represents the configuration of the storage backend. type StorageConfiguration struct { - Mongo *MongoStorageConfiguration `yaml:"mongo"` Local *LocalStorageConfiguration `yaml:"local"` + SQL *SQLStorageConfiguration `yaml:"sql"` } diff --git a/configuration/validator/storage.go b/configuration/validator/storage.go new file mode 100644 index 00000000..3633be38 --- /dev/null +++ b/configuration/validator/storage.go @@ -0,0 +1,36 @@ +package validator + +import ( + "errors" + + "github.com/clems4ever/authelia/configuration/schema" +) + +// ValidateSQLStorage validates storage configuration. +func ValidateSQLStorage(configuration *schema.StorageConfiguration, validator *schema.StructValidator) { + if configuration.Local == nil && configuration.SQL == nil { + validator.Push(errors.New("A storage configuration must be provided. It could be 'local' or 'sql'")) + } + + if configuration.SQL != nil { + validateSQLConfiguration(configuration.SQL, validator) + } else if configuration.Local != nil { + validateLocalStorageConfiguration(configuration.Local, validator) + } +} + +func validateSQLConfiguration(configuration *schema.SQLStorageConfiguration, validator *schema.StructValidator) { + if configuration.Password != "" && configuration.Username == "" { + validator.Push(errors.New("Username and password must be provided")) + } + + if configuration.Database == "" { + validator.Push(errors.New("A database must be provided")) + } +} + +func validateLocalStorageConfiguration(configuration *schema.LocalStorageConfiguration, validator *schema.StructValidator) { + if configuration.Path == "" { + validator.Push(errors.New("A file path must be provided with key 'path'")) + } +} diff --git a/docs/build-and-dev.md b/docs/build-and-dev.md index 4afe8d7b..6cfde42f 100644 --- a/docs/build-and-dev.md +++ b/docs/build-and-dev.md @@ -44,8 +44,8 @@ To run the unit tests written, run: ### Integration tests Integration tests run with Mocha and are based on Selenium. They generally -require a complete environment made of several components like redis, mongo and a LDAP -to run. That's why [suites] have been created. At this point, the *basic* suite should +require a complete environment made of several components like redis, a SQL server and a +LDAP to run. That's why [suites] have been created. At this point, the *basic* suite should already be running and you can run the tests related to this suite with the following command: diff --git a/docs/deployment-dev.md b/docs/deployment-dev.md index b5c7ba60..9781fcf5 100644 --- a/docs/deployment-dev.md +++ b/docs/deployment-dev.md @@ -4,7 +4,7 @@ 2. [Deploy With Docker](#deploy-with-docker) 3. [Deploy nginx](#deploy-nginx) 4. [Discard components](#discard-components) - 1. [Discard MongoDB](#discard-mongodb) + 1. [Discard SQL Server](#discard-sql-server) 2. [Discard Redis](#discard-redis) 3. [Discard LDAP](#discard-ldap) 5. [FAQ](#faq) @@ -46,12 +46,11 @@ TODO ## Discard components -### Discard MongoDB +### Discard SQL server -There is an option in the configuration file to discard MongoDB and use -your local filesystem to store data in a sqlite3 database. This option will -therefore prevent you from running multiple instances of **Authelia** in -parallel. +There is an option in the configuration file to avoid using a SQL server and use +a local sqlite3 database instead. This option will therefore prevent you from running +multiple instances of **Authelia** in parallel. Consequently, this option is not meant to be used in production or at least not one that should scale out. diff --git a/docs/deployment-production.md b/docs/deployment-production.md index e95948b3..67555cd3 100644 --- a/docs/deployment-production.md +++ b/docs/deployment-production.md @@ -15,10 +15,10 @@ authentication and authorization requests for your entire infrastructure. As **Authelia** will be key to your architecture, it requires several components to make it highly-available. Deploying it in production means having an LDAP server for storing the information about the users, a Redis cache to -store the user sessions in a distributed manner, a MongoDB to persist user -configurations and one or more nginx reverse proxies configured to be used with -Authelia. With such a setup **Authelia** can easily be scaled to multiple instances -to evenly handle the traffic. +store the user sessions in a distributed manner, a SQL server like MariaDB to +persist user configurations and one or more nginx reverse proxies configured to +be used with Authelia. With such a setup **Authelia** can easily be scaled to +multiple instances to evenly handle the traffic. **NOTE:** If you don't have all those components, don't worry, there is a way to deploy **Authelia** with only nginx. This is described in [Deployment for Devs]. diff --git a/docs/suites.md b/docs/suites.md index e34228c9..386ea538 100644 --- a/docs/suites.md +++ b/docs/suites.md @@ -3,18 +3,18 @@ Authelia is a single component in interaction with many others. Consequently, testing the features is not as easy as we might think. In order to solve this problem, Authelia came up with the concept of suite which is a kind of virtual environment for Authelia, it allows to create an environment made of -components such as nginx, redis or mongo in which Authelia can run and be tested. +components such as nginx, redis or mariadb in which Authelia can run and be tested. This abstraction allows to prepare an environment for manual testing during development and also to craft and run integration tests efficiently. ## Start a suite. -Starting a suite called *basic* is done with the following command: +Starting a suite called *Standalone* is done with the following command: - authelia-scripts suites start basic + authelia-scripts suites setup Standalone -It will start the suite and block until you hit ctrl-c to stop the suite. +It will deploy the environment of the suite and block until you hit ctrl-c to stop the suite. ## Run tests of a suite @@ -28,12 +28,12 @@ and this will run the tests related to the running suite. ### Run tests of non-running suite -However, if no suite is running and you still want to test a particular suite like *high-availability*. +However, if no suite is running and you still want to test a particular suite like *HighAvailability*. You can do so with the next command: - authelia-scripts suites test high-availability + authelia-scripts suites test HighAvailability -This command will run the tests for the *high-availability* suite. Beware that running tests of a +This command will run the tests for the *HighAvailability* suite. Beware that running tests of a non-running suite implies the tests run against the distributable version of Authelia instead of the current development version. If you made some patches, you must build the distributable version before running the test command: diff --git a/example/compose/authelia/resources/run.sh b/example/compose/authelia/resources/run.sh index e3b798c3..cc62a502 100755 --- a/example/compose/authelia/resources/run.sh +++ b/example/compose/authelia/resources/run.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -e + # Build the binary go build -o /tmp/authelia/authelia-tmp cmd/authelia/main.go diff --git a/example/compose/mariadb/docker-compose.yml b/example/compose/mariadb/docker-compose.yml new file mode 100644 index 00000000..2b440d4f --- /dev/null +++ b/example/compose/mariadb/docker-compose.yml @@ -0,0 +1,11 @@ +version: "3" +services: + mariadb: + image: mariadb:10.4.10 + environment: + - MYSQL_ROOT_PASSWORD=rootpassword + - MYSQL_USER=admin + - MYSQL_PASSWORD=password + - MYSQL_DATABASE=authelia + networks: + - authelianet \ No newline at end of file diff --git a/example/kube/README.md b/example/kube/README.md index 13d0225c..01e021a0 100644 --- a/example/kube/README.md +++ b/example/kube/README.md @@ -51,7 +51,7 @@ manual intervention. ## What do I need to know to deploy it in my cluster? -Given your cluster already runs a LDAP server, a Redis, a Mongo database, +Given your cluster already runs a LDAP server, a Redis, a SQL database, a SMTP server and a nginx ingress-controller, you can deploy **Authelia** and update your ingress configurations. An example is provided [here](./authelia). diff --git a/example/kube/authelia/configs/config.yml b/example/kube/authelia/configs/config.yml index 8c67c370..35738711 100644 --- a/example/kube/authelia/configs/config.yml +++ b/example/kube/authelia/configs/config.yml @@ -2,88 +2,25 @@ # Authelia configuration # ############################################################### -# The port to listen on port: 80 - -# Log level -# -# Level of verbosity for logs logs_level: debug - jwt_secret: an_unsecure_secret -# Default redirection URL -# -# If user tries to authenticate without any referer, Authelia -# does not know where to redirect the user to at the end of the -# authentication process. -# This parameter allows you to specify the default redirection -# URL Authelia will use in such a case. -# -# Note: this parameter is optional. If not provided, user won't -# be redirected upon successful authentication. default_redirection_url: https://home.example.com:8080 -# The authentication backend to use for verifying user passwords -# and retrieve information such as email address and groups -# users belong to. -# -# There are two supported backends: `ldap` and `file`. authentication_backend: - # LDAP backend configuration. - # - # This backend allows Authelia to be scaled to more - # than one instance and therefore is recommended for - # production. ldap: - # The url of the ldap server - url: ldap-service:389 - - # The base dn for every entries + url: ldap-service:389 base_dn: dc=example,dc=com - - # An additional dn to define the scope to all users additional_users_dn: ou=users - - # The users filter used to find the user DN - # {0} is a matcher replaced by username. - # 'cn={0}' by default. users_filter: (cn={0}) - - # An additional dn to define the scope of groups additional_groups_dn: ou=groups - - # The groups filter used for retrieving groups of a given user. - # {0} is a matcher replaced by username. - # {dn} is a matcher replaced by user DN. - # {uid} is a matcher replaced by user uid. - # 'member={dn}' by default. groups_filter: (&(member={dn})(objectclass=groupOfNames)) - - # The attribute holding the name of the group group_name_attribute: cn - - # The attribute holding the mail address of the user mail_attribute: mail - - # The username and password of the admin user. user: cn=admin,dc=example,dc=com password: password - # File backend configuration. - # - # With this backend, the users database is stored in a file - # which is updated when users reset their passwords. - # Therefore, this backend is meant to be used in a dev environment - # and not in production since it prevents Authelia to be scaled to - # more than one instance. - # - ## file: - ## path: ./users_database.yml - -# Access Control -# -# For more details about the configuration see config.template.yml at the root of the repo. access_control: default_policy: deny @@ -137,68 +74,27 @@ access_control: policy: two_factor -# Configuration of session cookies -# -# The session cookies identify the user once logged in. session: - # The secret to encrypt the session cookie. secret: unsecure_password - - # The time in ms before the cookie expires and session is reset. expiration: 3600000 # 1 hour - - # The inactivity time in ms before the session is reset. inactivity: 300000 # 5 minutes - - # The domain to protect. - # Note: the authenticator must also be in that domain. If empty, the cookie - # is restricted to the subdomain of the issuer. domain: example.com - - # The redis connection details redis: host: redis-service port: 6379 -# Configuration of the authentication regulation mechanism. -# -# This mechanism prevents attackers from brute forcing the first factor. -# It bans the user if too many attempts are done in a short period of -# time. regulation: - # The number of failed login attempts before user is banned. - # Set it to 0 for disabling regulation. max_retries: 3 - - # The length of time between login attempts before user is banned. find_time: 120 - - # The length of time before a banned user can login again. ban_time: 300 -# Configuration of the storage backend used to store data and secrets. -# -# You must use only an available configuration: local, mongo storage: - # The directory where the DB files will be saved - # local: /var/lib/authelia/store - - # Settings to connect to mongo server - mongo: - url: mongodb://mongo-service + sql: + host: mariadb-service + port: 3306 database: authelia -# Configuration of the notification system. -# -# Notifications are sent to users when they require a password reset, a u2f -# registration or a TOTP registration. -# Use only an available configuration: filesystem, gmail notifier: - # For testing purpose, notifications can be sent in a file - # filesystem: - # filename: /tmp/authelia/notification.txt - - # Use a SMTP server for sending notifications smtp: host: 'mailcatcher-service' port: 1025 diff --git a/example/kube/bootstrap.sh b/example/kube/bootstrap.sh index 76f18d13..cb5f2f6f 100755 --- a/example/kube/bootstrap.sh +++ b/example/kube/bootstrap.sh @@ -20,7 +20,7 @@ start_dashboard() { kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep admin-user | awk '{print $1}') } -# Spawn Redis and Mongo as backend for Authelia +# Spawn Redis and storage backend # Please note they are not configured to be distributed on several machines start_storage() { kubectl apply -f storage diff --git a/example/kube/storage/mariadb.yml b/example/kube/storage/mariadb.yml new file mode 100644 index 00000000..137fce07 --- /dev/null +++ b/example/kube/storage/mariadb.yml @@ -0,0 +1,54 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mariadb + namespace: authelia + labels: + app: mariadb +spec: + replicas: 1 + selector: + matchLabels: + app: mariadb + template: + metadata: + labels: + app: mariadb + spec: + containers: + - name: mariadb + image: mariadb:10.4.10 + ports: + - containerPort: 3306 + env: + - name: SLAPD_ORGANISATION + value: MyCompany + - name: MYSQL_ROOT_PASSWORD + value: rootpassword + - name: MYSQL_USER + value: admin + - name: MYSQL_PASSWORD + value: password + - name: MYSQL_DATABASE + value: authelia + volumeMounts: + - name: data-volume + mountPath: /var/lib/mysql + volumes: + - name: data-volume + hostPath: + path: /data/storage/mysql + +--- +apiVersion: v1 +kind: Service +metadata: + name: mariadb-service + namespace: authelia +spec: + selector: + app: mariadb + ports: + - protocol: TCP + port: 3306 diff --git a/example/kube/storage/mongo.yml b/example/kube/storage/mongo.yml deleted file mode 100644 index cf5e9238..00000000 --- a/example/kube/storage/mongo.yml +++ /dev/null @@ -1,48 +0,0 @@ ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: mongo - namespace: authelia - labels: - app: mongo -spec: - replicas: 1 - selector: - matchLabels: - app: mongo - template: - metadata: - labels: - app: mongo - spec: - containers: - - name: mongo - image: mongo:3.4 - ports: - - containerPort: 27017 - volumeMounts: - - name: data-volume - mountPath: /data/db - - name: config-volume - mountPath: /data/configdb - volumes: - - name: data-volume - hostPath: - path: /data/storage/mongo/data - - name: config-volume - hostPath: - path: /data/storage/mongo/config - ---- -apiVersion: v1 -kind: Service -metadata: - name: mongo-service - namespace: authelia -spec: - selector: - app: mongo - ports: - - protocol: TCP - port: 27017 diff --git a/go.mod b/go.mod index 2b57f472..2610a891 100644 --- a/go.mod +++ b/go.mod @@ -5,19 +5,15 @@ go 1.13 require ( github.com/Workiva/go-datastructures v1.0.50 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a - github.com/cespare/reflex v0.2.0 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74 github.com/fasthttp/router v0.5.2 github.com/fasthttp/session v1.1.3 - github.com/go-stack/stack v1.8.0 // indirect + github.com/go-sql-driver/mysql v1.4.1 github.com/golang/mock v1.3.1 - github.com/golang/snappy v0.0.1 // indirect - github.com/google/martian v2.1.0+incompatible - github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/kr/pty v1.1.8 // indirect github.com/mattn/go-sqlite3 v1.11.0 - github.com/ogier/pflag v0.0.1 // indirect + github.com/onsi/ginkgo v1.10.3 // indirect + github.com/onsi/gomega v1.7.1 // indirect github.com/otiai10/copy v1.0.2 github.com/pquerna/otp v1.2.0 github.com/simia-tech/crypt v0.2.0 @@ -27,9 +23,7 @@ require ( github.com/tebeka/selenium v0.9.9 github.com/tstranex/u2f v1.0.0 github.com/valyala/fasthttp v1.6.0 - github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect - github.com/xdg/stringprep v1.0.0 // indirect - go.mongodb.org/mongo-driver v1.1.2 + google.golang.org/appengine v1.6.5 // indirect gopkg.in/ldap.v3 v3.1.0 gopkg.in/yaml.v2 v2.2.4 ) diff --git a/go.sum b/go.sum index 7ab33e28..6c369cae 100644 --- a/go.sum +++ b/go.sum @@ -19,15 +19,11 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= -github.com/cespare/reflex v0.2.0 h1:6d9WpWJseKjJvZEevKP7Pk42nPx2+BUTqmhNk8wZPwM= -github.com/cespare/reflex v0.2.0/go.mod h1:ooqOLJ4algvHP/oYvKWfWJ9tFUzCLDk5qkIJduMYrgI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -43,9 +39,8 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4= github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 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= @@ -53,9 +48,8 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 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/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -73,18 +67,17 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +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 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/klauspost/compress v1.8.2 h1:Bx0qjetmNjdFXASH02NSAREKpiaDwkO1DRZ3dV2KCcs= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 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/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -95,11 +88,16 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/ogier/pflag v0.0.1 h1:RW6JSWSu/RkSatfcLtogGfFgpim5p7ARQ10ECk5O750= -github.com/ogier/pflag v0.0.1/go.mod h1:zkFki7tvTa0tafRvTBIZTvzYyAu6kQhPZFnshFFPE+g= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/otiai10/copy v1.0.2 h1:DDNipYy6RkIkjMwy+AWzgKiNTyj2RUI9yEMeETEpVyc= github.com/otiai10/copy v1.0.2/go.mod h1:c7RpqBkwMom4bYTSkLSym4VSJz/XtncWRAj/J4PEIMY= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95 h1:+OLn68pqasWca0z5ryit9KGfp3sUsW4Lqg32iRMJyzs= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/mint v1.3.0 h1:Ady6MKVezQwHBkGzLFbrsywyp09Ah7rkmfjV3Bcr5uc= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -149,13 +147,7 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC github.com/valyala/fasthttp v1.6.0 h1:uWF8lgKmeaIewWVPwi4GRq2P6+R46IgYZdxWtM+GtEY= github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= -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 v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= -github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -go.mongodb.org/mongo-driver v1.1.2 h1:jxcFYjlkl8xaERsgLo+RNquI0epW6zuy/ZRQs6jnrFA= -go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -175,6 +167,7 @@ golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 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/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/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -195,6 +188,7 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ 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/sys v0.0.0-20180830151530-49385e6e1522/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-20181116161606-93218def8b18/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -229,7 +223,10 @@ google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +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/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= @@ -241,9 +238,14 @@ google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiq google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= +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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ldap.v3 v3.1.0 h1:DIDWEjI7vQWREh0S8X5/NFPCZ3MCVd55LmXKPW4XLGE= gopkg.in/ldap.v3 v3.1.0/go.mod h1:dQjCc0R0kfyFjIlWNMH1DORwUASZyDxo2Ry1B51dXaQ= +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.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/storage/mongo_provider.go b/storage/mongo_provider.go deleted file mode 100644 index 4326929e..00000000 --- a/storage/mongo_provider.go +++ /dev/null @@ -1,379 +0,0 @@ -package storage - -import ( - "context" - "time" - - "github.com/clems4ever/authelia/configuration/schema" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" - - "github.com/clems4ever/authelia/models" -) - -const ( - prefered2FAMethodCollection = "prefered_2fa_method" - identityValidationTokensCollection = "identity_validation_tokens" - authenticationLogsCollection = "authentication_logs" - u2fRegistrationsCollection = "u2f_devices" - totpSecretsCollection = "totp_secrets" -) - -// MongoProvider is a storage provider persisting data in a SQLite database. -type MongoProvider struct { - configuration schema.MongoStorageConfiguration -} - -// NewMongoProvider construct a mongo provider. -func NewMongoProvider(configuration schema.MongoStorageConfiguration) *MongoProvider { - return &MongoProvider{configuration} -} - -func (p *MongoProvider) connect() (*mongo.Client, error) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - clientOptions := options.Client().ApplyURI(p.configuration.URL) - - if p.configuration.Auth.Username != "" && p.configuration.Auth.Password != "" { - credentials := options.Credential{ - Username: p.configuration.Auth.Username, - Password: p.configuration.Auth.Password, - } - clientOptions.SetAuth(credentials) - } - return mongo.Connect(ctx, clientOptions) -} - -type prefered2FAMethodDocument struct { - UserID string `bson:"userId"` - Method string `bson:"method"` -} - -// LoadPrefered2FAMethod load the prefered method for 2FA from sqlite db. -func (p *MongoProvider) LoadPrefered2FAMethod(username string) (string, error) { - client, err := p.connect() - if err != nil { - return "", nil - } - defer client.Disconnect(context.Background()) - - collection := client. - Database(p.configuration.Database). - Collection(prefered2FAMethodCollection) - - res := prefered2FAMethodDocument{} - err = collection.FindOne(context.Background(), - bson.M{"userId": username}). - Decode(&res) - - if err != nil { - if err == mongo.ErrNoDocuments { - return "", nil - } - return "", err - } - - return res.Method, nil -} - -// SavePrefered2FAMethod save the prefered method for 2FA in sqlite db. -func (p *MongoProvider) SavePrefered2FAMethod(username string, method string) error { - client, err := p.connect() - if err != nil { - return nil - } - defer client.Disconnect(context.Background()) - - collection := client. - Database(p.configuration.Database). - Collection(prefered2FAMethodCollection) - - updateOptions := options.ReplaceOptions{} - updateOptions.SetUpsert(true) - _, err = collection.ReplaceOne(context.Background(), - bson.M{"userId": username}, - bson.M{"userId": username, "method": method}, - &updateOptions) - - if err != nil { - return err - } - - return nil -} - -// IdentityTokenDocument model for the identiy token documents. -type IdentityTokenDocument struct { - Token string `bson:"token"` -} - -// FindIdentityVerificationToken look for an identity verification token in DB. -func (p *MongoProvider) FindIdentityVerificationToken(token string) (bool, error) { - client, err := p.connect() - if err != nil { - return false, nil - } - defer client.Disconnect(context.Background()) - - collection := client. - Database(p.configuration.Database). - Collection(identityValidationTokensCollection) - - res := IdentityTokenDocument{} - err = collection.FindOne(context.Background(), - bson.M{"token": token}).Decode(&res) - - if err != nil { - if err == mongo.ErrNoDocuments { - return false, nil - } - return false, err - } - - return true, nil -} - -// SaveIdentityVerificationToken save an identity verification token in DB. -func (p *MongoProvider) SaveIdentityVerificationToken(token string) error { - client, err := p.connect() - if err != nil { - return nil - } - defer client.Disconnect(context.Background()) - - collection := client. - Database(p.configuration.Database). - Collection(identityValidationTokensCollection) - - options := options.InsertOneOptions{} - _, err = collection.InsertOne(context.Background(), - bson.M{"token": token}, - &options) - - if err != nil { - return err - } - - return nil -} - -// RemoveIdentityVerificationToken remove an identity verification token from the DB. -func (p *MongoProvider) RemoveIdentityVerificationToken(token string) error { - client, err := p.connect() - if err != nil { - return nil - } - defer client.Disconnect(context.Background()) - - collection := client. - Database(p.configuration.Database). - Collection(identityValidationTokensCollection) - - options := options.DeleteOptions{} - _, err = collection.DeleteOne(context.Background(), - bson.M{"token": token}, - &options) - - if err != nil { - return err - } - return nil -} - -// TOTPSecretDocument model of document storing TOTP secrets -type TOTPSecretDocument struct { - UserID string `bson:"userId"` - Secret string `bson:"secret"` -} - -// SaveTOTPSecret save a TOTP secret of a given user. -func (p *MongoProvider) SaveTOTPSecret(username string, secret string) error { - client, err := p.connect() - if err != nil { - return nil - } - defer client.Disconnect(context.Background()) - - collection := client. - Database(p.configuration.Database). - Collection(totpSecretsCollection) - - options := options.ReplaceOptions{} - options.SetUpsert(true) - _, err = collection.ReplaceOne(context.Background(), - bson.M{"userId": username}, - bson.M{"userId": username, "secret": secret}, - &options) - - if err != nil { - return err - } - - return nil -} - -// LoadTOTPSecret load a TOTP secret given a username. -func (p *MongoProvider) LoadTOTPSecret(username string) (string, error) { - client, err := p.connect() - if err != nil { - return "", nil - } - defer client.Disconnect(context.Background()) - - collection := client. - Database(p.configuration.Database). - Collection(totpSecretsCollection) - - res := TOTPSecretDocument{} - err = collection.FindOne(context.Background(), - bson.M{"userId": username}).Decode(&res) - - if err != nil { - if err == mongo.ErrNoDocuments { - return "", nil - } - return "", err - } - return res.Secret, nil -} - -// U2FDeviceDocument model of document storing U2F device -type U2FDeviceDocument struct { - UserID string `bson:"userId"` - DeviceHandle []byte `bson:"deviceHandle"` -} - -// SaveU2FDeviceHandle save a registered U2F device registration blob. -func (p *MongoProvider) SaveU2FDeviceHandle(username string, deviceBytes []byte) error { - client, err := p.connect() - if err != nil { - return nil - } - defer client.Disconnect(context.Background()) - - collection := client. - Database(p.configuration.Database). - Collection(u2fRegistrationsCollection) - - options := options.ReplaceOptions{} - options.SetUpsert(true) - - _, err = collection.ReplaceOne(context.Background(), - bson.M{"userId": username}, - bson.M{"userId": username, "deviceHandle": deviceBytes}, - &options) - - if err != nil { - return err - } - - return nil -} - -// LoadU2FDeviceHandle load a U2F device registration blob for a given username. -func (p *MongoProvider) LoadU2FDeviceHandle(username string) ([]byte, error) { - client, err := p.connect() - if err != nil { - return nil, nil - } - defer client.Disconnect(context.Background()) - - collection := client. - Database(p.configuration.Database). - Collection(u2fRegistrationsCollection) - - res := U2FDeviceDocument{} - err = collection.FindOne(context.Background(), - bson.M{"userId": username}).Decode(&res) - - if err != nil { - if err == mongo.ErrNoDocuments { - return nil, ErrNoU2FDeviceHandle - } - return nil, err - } - - return res.DeviceHandle, nil -} - -// AuthenticationLogDocument model of document storing authentication logs -type AuthenticationLogDocument struct { - UserID string `bson:"userId"` - Time time.Time `bson:"time"` - Success bool `bson:"success"` -} - -// AppendAuthenticationLog append a mark to the authentication log. -func (p *MongoProvider) AppendAuthenticationLog(attempt models.AuthenticationAttempt) error { - client, err := p.connect() - if err != nil { - return nil - } - defer client.Disconnect(context.Background()) - - collection := client. - Database(p.configuration.Database). - Collection(authenticationLogsCollection) - - options := options.InsertOneOptions{} - _, err = collection.InsertOne(context.Background(), - bson.M{ - "userId": attempt.Username, - "time": attempt.Time, - "success": attempt.Successful, - }, - &options) - - if err != nil { - return err - } - - return nil -} - -// LoadLatestAuthenticationLogs retrieve the latest marks from the authentication log. -func (p *MongoProvider) LoadLatestAuthenticationLogs(username string, fromDate time.Time) ([]models.AuthenticationAttempt, error) { - client, err := p.connect() - if err != nil { - return nil, nil - } - defer client.Disconnect(context.Background()) - - collection := client. - Database(p.configuration.Database). - Collection(authenticationLogsCollection) - - options := options.FindOptions{} - options.SetSort(bson.M{"time": -1}) - cursor, err := collection.Find(context.Background(), - bson.M{ - "$and": bson.M{ - "userId": username, - "time": bson.M{"$gt": fromDate}, - }, - }) - - if err != nil { - if err == mongo.ErrNoDocuments { - return nil, nil - } - return nil, err - } - - res := []AuthenticationLogDocument{} - cursor.All(context.Background(), &res) - - attempts := []models.AuthenticationAttempt{} - for _, r := range res { - attempt := models.AuthenticationAttempt{ - Username: r.UserID, - Time: r.Time, - Successful: r.Success, - } - attempts = append(attempts, attempt) - } - - return attempts, nil -} diff --git a/storage/mysql_provider.go b/storage/mysql_provider.go new file mode 100644 index 00000000..116fd0eb --- /dev/null +++ b/storage/mysql_provider.go @@ -0,0 +1,66 @@ +package storage + +import ( + "database/sql" + "fmt" + "time" + + "github.com/clems4ever/authelia/configuration/schema" + "github.com/clems4ever/authelia/logging" + _ "github.com/go-sql-driver/mysql" // Load the MySQL Driver used in the connection string. +) + +// MySQLProvider is a MySQL provider +type MySQLProvider struct { + SQLProvider +} + +// NewSQLProvider a SQL provider +func NewSQLProvider(configuration schema.SQLStorageConfiguration) *MySQLProvider { + connectionString := configuration.Username + + if configuration.Password != "" { + connectionString += fmt.Sprintf(":%s", configuration.Password) + } + + if connectionString != "" { + connectionString += "@" + } + + address := configuration.Host + if configuration.Port > 0 { + address += fmt.Sprintf(":%d", configuration.Port) + } + connectionString += fmt.Sprintf("tcp(%s)", address) + + if configuration.Database != "" { + connectionString += fmt.Sprintf("/%s", configuration.Database) + } + + fmt.Println(connectionString) + + db, err := sql.Open("mysql", connectionString) + if err != nil { + logging.Logger().Fatalf("Unable to connect to SQL database: %v", err) + } + + for i := 0; i < 3; i++ { + if err = db.Ping(); err == nil { + logging.Logger().Debug("Connection to the database is established") + break + } + + if i == 2 { + logging.Logger().Fatal("Aborting because connection to database failed") + } + + logging.Logger().Errorf("Unable to ping database retrying in 10 seconds. error: %v", err) + time.Sleep(10 * time.Second) + } + + provider := MySQLProvider{} + if err := provider.initialize(db); err != nil { + logging.Logger().Fatalf("Unable to initialize SQL database: %v", err) + } + return &provider +} diff --git a/storage/sql_provider.go b/storage/sql_provider.go new file mode 100644 index 00000000..b8d28476 --- /dev/null +++ b/storage/sql_provider.go @@ -0,0 +1,212 @@ +package storage + +import ( + "database/sql" + "time" + + "github.com/clems4ever/authelia/models" +) + +// SQLProvider is a storage provider persisting data in a SQL database. +type SQLProvider struct { + db *sql.DB +} + +func (p *SQLProvider) initialize(db *sql.DB) error { + p.db = db + + _, err := db.Exec("CREATE TABLE IF NOT EXISTS SecondFactorPreferences (username VARCHAR(100) PRIMARY KEY, method VARCHAR(10))") + if err != nil { + return err + } + + _, err = db.Exec("CREATE TABLE IF NOT EXISTS IdentityVerificationTokens (token VARCHAR(512))") + if err != nil { + return err + } + + _, err = db.Exec("CREATE TABLE IF NOT EXISTS TOTPSecrets (username VARCHAR(100) PRIMARY KEY, secret VARCHAR(64))") + if err != nil { + return err + } + + _, err = db.Exec("CREATE TABLE IF NOT EXISTS U2FDeviceHandles (username VARCHAR(100) PRIMARY KEY, deviceHandle BLOB)") + if err != nil { + return err + } + + _, err = db.Exec("CREATE TABLE IF NOT EXISTS AuthenticationLogs (username VARCHAR(100), successful BOOL, time INTEGER)") + if err != nil { + return err + } + + _, err = db.Exec("CREATE INDEX IF NOT EXISTS time ON AuthenticationLogs (time);") + if err != nil { + return err + } + + _, err = db.Exec("CREATE INDEX IF NOT EXISTS username ON AuthenticationLogs (username);") + if err != nil { + return err + } + return nil +} + +// LoadPrefered2FAMethod load the prefered method for 2FA from sqlite db. +func (p *SQLProvider) LoadPrefered2FAMethod(username string) (string, error) { + stmt, err := p.db.Prepare("SELECT method FROM SecondFactorPreferences WHERE username=?") + if err != nil { + return "", err + } + rows, err := stmt.Query(username) + defer rows.Close() + if err != nil { + return "", err + } + if rows.Next() { + var method string + err = rows.Scan(&method) + if err != nil { + return "", err + } + return method, nil + } + return "", nil +} + +// SavePrefered2FAMethod save the prefered method for 2FA in sqlite db. +func (p *SQLProvider) SavePrefered2FAMethod(username string, method string) error { + stmt, err := p.db.Prepare("REPLACE INTO SecondFactorPreferences (username, method) VALUES (?, ?)") + if err != nil { + return err + } + _, err = stmt.Exec(username, method) + return err +} + +// FindIdentityVerificationToken look for an identity verification token in DB. +func (p *SQLProvider) FindIdentityVerificationToken(token string) (bool, error) { + stmt, err := p.db.Prepare("SELECT token FROM IdentityVerificationTokens WHERE token=?") + if err != nil { + return false, err + } + var found string + err = stmt.QueryRow(token).Scan(&found) + if err != nil { + if err == sql.ErrNoRows { + return false, nil + } + return false, err + } + return true, nil +} + +// SaveIdentityVerificationToken save an identity verification token in DB. +func (p *SQLProvider) SaveIdentityVerificationToken(token string) error { + stmt, err := p.db.Prepare("INSERT INTO IdentityVerificationTokens (token) VALUES (?)") + if err != nil { + return err + } + _, err = stmt.Exec(token) + return err +} + +// RemoveIdentityVerificationToken remove an identity verification token from the DB. +func (p *SQLProvider) RemoveIdentityVerificationToken(token string) error { + stmt, err := p.db.Prepare("DELETE FROM IdentityVerificationTokens WHERE token=?") + if err != nil { + return err + } + _, err = stmt.Exec(token) + return err +} + +// SaveTOTPSecret save a TOTP secret of a given user. +func (p *SQLProvider) SaveTOTPSecret(username string, secret string) error { + stmt, err := p.db.Prepare("REPLACE INTO TOTPSecrets (username, secret) VALUES (?, ?)") + if err != nil { + return err + } + _, err = stmt.Exec(username, secret) + return err +} + +// LoadTOTPSecret load a TOTP secret given a username. +func (p *SQLProvider) LoadTOTPSecret(username string) (string, error) { + stmt, err := p.db.Prepare("SELECT secret FROM TOTPSecrets WHERE username=?") + if err != nil { + return "", err + } + var secret string + err = stmt.QueryRow(username).Scan(&secret) + if err != nil { + if err == sql.ErrNoRows { + return "", nil + } + return "", err + } + return secret, nil +} + +// SaveU2FDeviceHandle save a registered U2F device registration blob. +func (p *SQLProvider) SaveU2FDeviceHandle(username string, keyHandle []byte) error { + stmt, err := p.db.Prepare("REPLACE INTO U2FDeviceHandles (username, deviceHandle) VALUES (?, ?)") + if err != nil { + return err + } + _, err = stmt.Exec(username, keyHandle) + return err +} + +// LoadU2FDeviceHandle load a U2F device registration blob for a given username. +func (p *SQLProvider) LoadU2FDeviceHandle(username string) ([]byte, error) { + stmt, err := p.db.Prepare("SELECT deviceHandle FROM U2FDeviceHandles WHERE username=?") + if err != nil { + return nil, err + } + var deviceHandle []byte + err = stmt.QueryRow(username).Scan(&deviceHandle) + if err != nil { + if err == sql.ErrNoRows { + return nil, ErrNoU2FDeviceHandle + } + return nil, err + } + return deviceHandle, nil +} + +// AppendAuthenticationLog append a mark to the authentication log. +func (p *SQLProvider) AppendAuthenticationLog(attempt models.AuthenticationAttempt) error { + stmt, err := p.db.Prepare("INSERT INTO AuthenticationLogs (username, successful, time) VALUES (?, ?, ?)") + if err != nil { + return err + } + _, err = stmt.Exec(attempt.Username, attempt.Successful, attempt.Time.Unix()) + return err +} + +// LoadLatestAuthenticationLogs retrieve the latest marks from the authentication log. +func (p *SQLProvider) LoadLatestAuthenticationLogs(username string, fromDate time.Time) ([]models.AuthenticationAttempt, error) { + rows, err := p.db.Query("SELECT successful, time FROM AuthenticationLogs WHERE time>? AND username=? ORDER BY time DESC", + fromDate.Unix(), username) + + if err != nil { + return nil, err + } + + attempts := make([]models.AuthenticationAttempt, 0, 10) + for rows.Next() { + attempt := models.AuthenticationAttempt{ + Username: username, + } + var t int64 + err = rows.Scan(&attempt.Successful, &t) + attempt.Time = time.Unix(t, 0) + + if err != nil { + return nil, err + } + attempts = append(attempts, attempt) + } + return attempts, nil +} diff --git a/storage/sqlite_provider.go b/storage/sqlite_provider.go index 5c5e9ae4..6efe7003 100644 --- a/storage/sqlite_provider.go +++ b/storage/sqlite_provider.go @@ -2,228 +2,26 @@ package storage import ( "database/sql" - "time" - - "github.com/clems4ever/authelia/models" "github.com/clems4ever/authelia/logging" _ "github.com/mattn/go-sqlite3" // Load the SQLite Driver used in the connection string. ) -// SQLiteProvider is a storage provider persisting data in a SQLite database. +// SQLiteProvider is a sqlite3 provider type SQLiteProvider struct { - db *sql.DB + SQLProvider } // NewSQLiteProvider construct a sqlite provider. func NewSQLiteProvider(path string) *SQLiteProvider { - provider := SQLiteProvider{} - err := provider.initialize(path) + db, err := sql.Open("sqlite3", path) if err != nil { logging.Logger().Fatalf("Unable to create SQLite database %s: %s", path, err) } + + provider := SQLiteProvider{} + if err := provider.initialize(db); err != nil { + logging.Logger().Fatalf("Unable to initialize SQLite database %s: %s", path, err) + } return &provider } - -func (p *SQLiteProvider) initialize(path string) error { - db, err := sql.Open("sqlite3", path) - if err != nil { - return err - } - p.db = db - - _, err = db.Exec("CREATE TABLE IF NOT EXISTS SecondFactorPreferences (username VARCHAR(100) PRIMARY KEY, method VARCHAR(10))") - if err != nil { - return err - } - - _, err = db.Exec("CREATE TABLE IF NOT EXISTS IdentityVerificationTokens (token VARCHAR(512))") - if err != nil { - return err - } - - _, err = db.Exec("CREATE TABLE IF NOT EXISTS TOTPSecrets (username VARCHAR(100) PRIMARY KEY, secret VARCHAR(64))") - if err != nil { - return err - } - - _, err = db.Exec("CREATE TABLE IF NOT EXISTS U2FDeviceHandles (username VARCHAR(100) PRIMARY KEY, deviceHandle BLOB)") - if err != nil { - return err - } - - _, err = db.Exec("CREATE TABLE IF NOT EXISTS AuthenticationLogs (username VARCHAR(100), successful BOOL, time INTEGER)") - if err != nil { - return err - } - - _, err = db.Exec("CREATE INDEX IF NOT EXISTS time ON AuthenticationLogs (time);") - if err != nil { - return err - } - - _, err = db.Exec("CREATE INDEX IF NOT EXISTS username ON AuthenticationLogs (username);") - if err != nil { - return err - } - return nil -} - -// LoadPrefered2FAMethod load the prefered method for 2FA from sqlite db. -func (p *SQLiteProvider) LoadPrefered2FAMethod(username string) (string, error) { - stmt, err := p.db.Prepare("SELECT method FROM SecondFactorPreferences WHERE username=?") - if err != nil { - return "", err - } - rows, err := stmt.Query(username) - defer rows.Close() - if err != nil { - return "", err - } - if rows.Next() { - var method string - err = rows.Scan(&method) - if err != nil { - return "", err - } - return method, nil - } - return "", nil -} - -// SavePrefered2FAMethod save the prefered method for 2FA in sqlite db. -func (p *SQLiteProvider) SavePrefered2FAMethod(username string, method string) error { - stmt, err := p.db.Prepare("INSERT OR REPLACE INTO SecondFactorPreferences (username, method) VALUES (?, ?)") - if err != nil { - return err - } - _, err = stmt.Exec(username, method) - return err -} - -// FindIdentityVerificationToken look for an identity verification token in DB. -func (p *SQLiteProvider) FindIdentityVerificationToken(token string) (bool, error) { - stmt, err := p.db.Prepare("SELECT token FROM IdentityVerificationTokens WHERE token=?") - if err != nil { - return false, err - } - var found string - err = stmt.QueryRow(token).Scan(&found) - if err != nil { - if err == sql.ErrNoRows { - return false, nil - } - return false, err - } - return true, nil -} - -// SaveIdentityVerificationToken save an identity verification token in DB. -func (p *SQLiteProvider) SaveIdentityVerificationToken(token string) error { - stmt, err := p.db.Prepare("INSERT INTO IdentityVerificationTokens (token) VALUES (?)") - if err != nil { - return err - } - _, err = stmt.Exec(token) - return err -} - -// RemoveIdentityVerificationToken remove an identity verification token from the DB. -func (p *SQLiteProvider) RemoveIdentityVerificationToken(token string) error { - stmt, err := p.db.Prepare("DELETE FROM IdentityVerificationTokens WHERE token=?") - if err != nil { - return err - } - _, err = stmt.Exec(token) - return err -} - -// SaveTOTPSecret save a TOTP secret of a given user. -func (p *SQLiteProvider) SaveTOTPSecret(username string, secret string) error { - stmt, err := p.db.Prepare("INSERT OR REPLACE INTO TOTPSecrets (username, secret) VALUES (?, ?)") - if err != nil { - return err - } - _, err = stmt.Exec(username, secret) - return err -} - -// LoadTOTPSecret load a TOTP secret given a username. -func (p *SQLiteProvider) LoadTOTPSecret(username string) (string, error) { - stmt, err := p.db.Prepare("SELECT secret FROM TOTPSecrets WHERE username=?") - if err != nil { - return "", err - } - var secret string - err = stmt.QueryRow(username).Scan(&secret) - if err != nil { - if err == sql.ErrNoRows { - return "", nil - } - return "", err - } - return secret, nil -} - -// SaveU2FDeviceHandle save a registered U2F device registration blob. -func (p *SQLiteProvider) SaveU2FDeviceHandle(username string, keyHandle []byte) error { - stmt, err := p.db.Prepare("INSERT OR REPLACE INTO U2FDeviceHandles (username, deviceHandle) VALUES (?, ?)") - if err != nil { - return err - } - _, err = stmt.Exec(username, keyHandle) - return err -} - -// LoadU2FDeviceHandle load a U2F device registration blob for a given username. -func (p *SQLiteProvider) LoadU2FDeviceHandle(username string) ([]byte, error) { - stmt, err := p.db.Prepare("SELECT deviceHandle FROM U2FDeviceHandles WHERE username=?") - if err != nil { - return nil, err - } - var deviceHandle []byte - err = stmt.QueryRow(username).Scan(&deviceHandle) - if err != nil { - if err == sql.ErrNoRows { - return nil, ErrNoU2FDeviceHandle - } - return nil, err - } - return deviceHandle, nil -} - -// AppendAuthenticationLog append a mark to the authentication log. -func (p *SQLiteProvider) AppendAuthenticationLog(attempt models.AuthenticationAttempt) error { - stmt, err := p.db.Prepare("INSERT INTO AuthenticationLogs (username, successful, time) VALUES (?, ?, ?)") - if err != nil { - return err - } - _, err = stmt.Exec(attempt.Username, attempt.Successful, attempt.Time.Unix()) - return err -} - -// LoadLatestAuthenticationLogs retrieve the latest marks from the authentication log. -func (p *SQLiteProvider) LoadLatestAuthenticationLogs(username string, fromDate time.Time) ([]models.AuthenticationAttempt, error) { - rows, err := p.db.Query("SELECT successful, time FROM AuthenticationLogs WHERE time>? AND username=? ORDER BY time DESC", - fromDate.Unix(), username) - - if err != nil { - return nil, err - } - - attempts := make([]models.AuthenticationAttempt, 0, 10) - for rows.Next() { - attempt := models.AuthenticationAttempt{ - Username: username, - } - var t int64 - err = rows.Scan(&attempt.Successful, &t) - attempt.Time = time.Unix(t, 0) - - if err != nil { - return nil, err - } - attempts = append(attempts, attempt) - } - return attempts, nil -} diff --git a/suites/HighAvailability/configuration.yml b/suites/HighAvailability/configuration.yml index bb58ce22..4548261b 100644 --- a/suites/HighAvailability/configuration.yml +++ b/suites/HighAvailability/configuration.yml @@ -220,19 +220,15 @@ regulation: # Configuration of the storage backend used to store data and secrets. # -# You must use only an available configuration: local, mongo +# You must use only an available configuration: local, sql storage: - # The directory where the DB files will be saved - ## local: - ## path: /var/lib/authelia/store - - # Settings to connect to mongo server - mongo: - url: mongodb://mongo + # Settings to connect to mariadb server + sql: + host: mariadb + port: 3306 database: authelia - auth: - username: authelia - password: authelia + username: admin + password: password # Configuration of the notification system. # diff --git a/suites/Mongo/configuration.yml b/suites/Mariadb/configuration.yml similarity index 94% rename from suites/Mongo/configuration.yml rename to suites/Mariadb/configuration.yml index 4907a2be..1aafd427 100644 --- a/suites/Mongo/configuration.yml +++ b/suites/Mariadb/configuration.yml @@ -22,12 +22,12 @@ session: # Configuration of the storage backend used to store data and secrets. i.e. totp data storage: - mongo: - url: mongodb://mongo + sql: + host: mariadb + port: 3306 database: authelia - auth: - username: authelia - password: authelia + username: admin + password: password # TOTP Issuer Name # diff --git a/suites/Mongo/users.yml b/suites/Mariadb/users.yml similarity index 100% rename from suites/Mongo/users.yml rename to suites/Mariadb/users.yml diff --git a/suites/docker.go b/suites/docker.go index 11434c83..a9d15e0e 100644 --- a/suites/docker.go +++ b/suites/docker.go @@ -1,6 +1,7 @@ package suites import ( + "fmt" "os" "os/exec" "strings" @@ -38,6 +39,13 @@ func (de *DockerEnvironment) Up(suitePath string) error { return cmd.Run() } +// Restart restarts a service +func (de *DockerEnvironment) Restart(suitePath, service string) error { + cmd := de.createCommandWithStdout(fmt.Sprintf("restart %s", service)) + cmd.Env = append(os.Environ(), "SUITE_PATH="+suitePath) + return cmd.Run() +} + // Down spawn a docker environment func (de *DockerEnvironment) Down(suitePath string) error { cmd := de.createCommandWithStdout("down -v") diff --git a/suites/suite_basic.go b/suites/suite_basic.go deleted file mode 100644 index 7af7286f..00000000 --- a/suites/suite_basic.go +++ /dev/null @@ -1,39 +0,0 @@ -package suites - -import ( - "time" -) - -var basicSuiteName = "Basic" - -func init() { - dockerEnvironment := NewDockerEnvironment([]string{ - "docker-compose.yml", - "example/compose/authelia/docker-compose.backend.yml", - "example/compose/authelia/docker-compose.frontend.yml", - "example/compose/nginx/backend/docker-compose.yml", - "example/compose/nginx/portal/docker-compose.yml", - "example/compose/smtp/docker-compose.yml", - }) - - setup := func(suitePath string) error { - if err := dockerEnvironment.Up(suitePath); err != nil { - return err - } - return waitUntilAutheliaIsReady(dockerEnvironment) - } - - teardown := func(suitePath string) error { - return dockerEnvironment.Down(suitePath) - } - - GlobalRegistry.Register(basicSuiteName, Suite{ - TestTimeout: 1 * time.Minute, - SetUp: setup, - SetUpTimeout: 5 * time.Minute, - TearDown: teardown, - TearDownTimeout: 1 * time.Minute, - Description: `This suite is used to test Authelia in a standalone -configuration with in-memory sessions and a local sqlite db stored on disk`, - }) -} diff --git a/suites/suite_basic_test.go b/suites/suite_basic_test.go deleted file mode 100644 index b03195d7..00000000 --- a/suites/suite_basic_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package suites - -import ( - "testing" -) - -type BasicSuite struct { - *SeleniumSuite -} - -func NewBasicSuite() *BasicSuite { - return &BasicSuite{SeleniumSuite: new(SeleniumSuite)} -} - -func TestBasicSuite(t *testing.T) { - RunTypescriptSuite(t, basicSuiteName) -} diff --git a/suites/suite_high_availability.go b/suites/suite_high_availability.go index 33d62cda..e2412a97 100644 --- a/suites/suite_high_availability.go +++ b/suites/suite_high_availability.go @@ -11,7 +11,7 @@ func init() { "docker-compose.yml", "example/compose/authelia/docker-compose.backend.yml", "example/compose/authelia/docker-compose.frontend.yml", - "example/compose/mongo/docker-compose.yml", + "example/compose/mariadb/docker-compose.yml", "example/compose/redis/docker-compose.yml", "example/compose/nginx/backend/docker-compose.yml", "example/compose/nginx/portal/docker-compose.yml", diff --git a/suites/suite_high_availability_test.go b/suites/suite_high_availability_test.go index 68d1d1c7..b78a4c88 100644 --- a/suites/suite_high_availability_test.go +++ b/suites/suite_high_availability_test.go @@ -14,6 +14,5 @@ func NewHighAvailabilitySuite() *HighAvailabilitySuite { func TestHighAvailabilitySuite(t *testing.T) { RunTypescriptSuite(t, highAvailabilitySuiteName) - TestRunOneFactor(t) } diff --git a/suites/suite_mongo.go b/suites/suite_mariadb.go similarity index 77% rename from suites/suite_mongo.go rename to suites/suite_mariadb.go index a68c7e29..0d3c94f2 100644 --- a/suites/suite_mongo.go +++ b/suites/suite_mariadb.go @@ -4,7 +4,7 @@ import ( "time" ) -var mongoSuiteName = "Mongo" +var mariadbSuiteName = "Mariadb" func init() { dockerEnvironment := NewDockerEnvironment([]string{ @@ -14,14 +14,12 @@ func init() { "example/compose/nginx/backend/docker-compose.yml", "example/compose/nginx/portal/docker-compose.yml", "example/compose/smtp/docker-compose.yml", - "example/compose/mongo/docker-compose.yml", + "example/compose/mariadb/docker-compose.yml", "example/compose/ldap/docker-compose.yml", }) setup := func(suitePath string) error { - err := dockerEnvironment.Up(suitePath) - - if err != nil { + if err := dockerEnvironment.Up(suitePath); err != nil { return err } @@ -33,9 +31,9 @@ func init() { return err } - GlobalRegistry.Register(mongoSuiteName, Suite{ + GlobalRegistry.Register(mariadbSuiteName, Suite{ SetUp: setup, - SetUpTimeout: 5 * time.Minute, + SetUpTimeout: 3 * time.Minute, TearDown: teardown, TearDownTimeout: 2 * time.Minute, }) diff --git a/suites/suite_mongo_test.go b/suites/suite_mariadb_test.go similarity index 51% rename from suites/suite_mongo_test.go rename to suites/suite_mariadb_test.go index 30e863eb..66fe6eed 100644 --- a/suites/suite_mongo_test.go +++ b/suites/suite_mariadb_test.go @@ -6,15 +6,15 @@ import ( "github.com/stretchr/testify/suite" ) -type MongoSuite struct { +type MariadbSuite struct { *SeleniumSuite } -func NewMongoSuite() *MongoSuite { - return &MongoSuite{SeleniumSuite: new(SeleniumSuite)} +func NewMariadbSuite() *MariadbSuite { + return &MariadbSuite{SeleniumSuite: new(SeleniumSuite)} } -func TestMongoSuite(t *testing.T) { +func TestMariadbSuite(t *testing.T) { suite.Run(t, NewOneFactorSuite()) suite.Run(t, NewTwoFactorSuite()) } diff --git a/suites/suite_standalone.go b/suites/suite_standalone.go index 7e8b2462..8bc33941 100644 --- a/suites/suite_standalone.go +++ b/suites/suite_standalone.go @@ -36,5 +36,7 @@ func init() { SetUpTimeout: 5 * time.Minute, TearDown: teardown, TearDownTimeout: 5 * time.Minute, + Description: `This suite is used to test Authelia in a standalone +configuration with in-memory sessions and a local sqlite db stored on disk`, }) } diff --git a/suites/suite_standalone_test.go b/suites/suite_standalone_test.go index 43699e3b..3d032732 100644 --- a/suites/suite_standalone_test.go +++ b/suites/suite_standalone_test.go @@ -17,4 +17,6 @@ func NewStandaloneSuite() *StandaloneSuite { func TestStandaloneSuite(t *testing.T) { suite.Run(t, NewOneFactorSuite()) suite.Run(t, NewTwoFactorSuite()) + + RunTypescriptSuite(t, standaloneSuiteName) } diff --git a/test/resources/config.yml b/test/resources/config.yml index 8403a339..27d328fa 100644 --- a/test/resources/config.yml +++ b/test/resources/config.yml @@ -2,140 +2,34 @@ # Authelia configuration # ############################################################### -# The port to listen on port: 9091 -# Log level -# -# Level of verbosity for logs logs_level: debug - jwt_secret: a_secret - -# Default redirection URL -# -# If user tries to authenticate without any referer, Authelia -# does not know where to redirect the user to at the end of the -# authentication process. -# This parameter allows you to specify the default redirection -# URL Authelia will use in such a case. -# -# Note: this parameter is optional. If not provided, user won't -# be redirected upon successful authentication. default_redirection_url: https://home.example.com:8080/ -# TOTP Issuer Name -# -# This will be the issuer name displayed in Google Authenticator -# See: https://github.com/google/google-authenticator/wiki/Key-Uri-Format for more info on issuer names totp: issuer: authelia.com -# Duo Push API -# -# Parameters used to contact the Duo API. Those are generated when you protect an application -# of type "Partner Auth API" in the management panel. duo_api: hostname: api-123456789.example.com integration_key: ABCDEF secret_key: 1234567890abcdefghifjkl -# The authentication backend to use for verifying user passwords -# and retrieve information such as email address and groups -# users belong to. -# -# There are two supported backends: `ldap` and `file`. authentication_backend: - # LDAP backend configuration. - # - # This backend allows Authelia to be scaled to more - # than one instance and therefore is recommended for - # production. ldap: - # The url of the ldap server url: ldap://127.0.0.1 - - # The base dn for every entries base_dn: dc=example,dc=com - - # An additional dn to define the scope to all users additional_users_dn: ou=users - - # The users filter used to find the user DN - # {0} is a matcher replaced by username. - # 'cn={0}' by default. - users_filter: cn={0} - - # An additional dn to define the scope of groups + users_filter: (cn={0}) additional_groups_dn: ou=groups - - # The groups filter used for retrieving groups of a given user. - # {0} is a matcher replaced by username. - # {dn} is a matcher replaced by user DN. - # {uid} is a matcher replaced by user uid. - # 'member={dn}' by default. groups_filter: (&(member={dn})(objectclass=groupOfNames)) - - # The attribute holding the name of the group group_name_attribute: cn - - # The attribute holding the mail address of the user mail_attribute: mail - - # The username and password of the admin user. user: cn=admin,dc=example,dc=com password: password - # File backend configuration. - # - # With this backend, the users database is stored in a file - # which is updated when users reset their passwords. - # Therefore, this backend is meant to be used in a dev environment - # and not in production since it prevents Authelia to be scaled to - # more than one instance. - # - ## file: - ## path: ./users_database.yml - - -# Access Control -# -# Access control is a list of rules defining the authorizations applied for one -# resource to users or group of users. -# -# If 'access_control' is not defined, ACL rules are disabled and the `bypass` -# rule is applied, i.e., access is allowed to anyone. Otherwise restrictions follow -# the rules defined. -# -# Note: One can use the wildcard * to match any subdomain. -# It must stand at the beginning of the pattern. (example: *.mydomain.com) -# -# Note: You must put patterns containing wildcards between simple quotes for the YAML -# to be syntaxically correct. -# -# Definition: A `rule` is an object with the following keys: `domain`, `subject`, -# `policy` and `resources`. -# -# - `domain` defines which domain or set of domains the rule applies to. -# -# - `subject` defines the subject to apply authorizations to. This parameter is -# optional and matching any user if not provided. If provided, the parameter -# represents either a user or a group. It should be of the form 'user:' -# or 'group:'. -# -# - `policy` is the policy to apply to resources. It must be either `bypass`, -# `one_factor`, `two_factor` or `deny`. -# -# - `resources` is a list of regular expressions that matches a set of resources to -# apply the policy to. This parameter is optional and matches any resource if not -# provided. -# -# Note: the order of the rules is important. The first policy matching -# (domain, resource, subject) applies. access_control: - # Default policy can either be `bypass`, `one_factor`, `two_factor` or `deny`. - # It is the policy applied to any resource if there is no policy to be applied - # to the user. default_policy: deny rules: @@ -194,86 +88,31 @@ access_control: subject: 'user:bob' policy: two_factor - -# Configuration of session cookies -# -# The session cookies identify the user once logged in. session: - # The name of the session cookie. (default: authelia_session). name: authelia_session - - # The secret to encrypt the session cookie. secret: unsecure_session_secret - - # The time in ms before the cookie expires and session is reset. expiration: 3600000 # 1 hour - - # The inactivity time in ms before the session is reset. inactivity: 300000 # 5 minutes - - # The domain to protect. - # Note: the authenticator must also be in that domain. If empty, the cookie - # is restricted to the subdomain of the issuer. domain: example.com - - # The redis connection details redis: host: 127.0.0.1 port: 6379 password: authelia -# Configuration of the authentication regulation mechanism. -# -# This mechanism prevents attackers from brute forcing the first factor. -# It bans the user if too many attempts are done in a short period of -# time. regulation: - # The number of failed login attempts before user is banned. - # Set it to 0 to disable regulation. max_retries: 3 - - # The time range during which the user can attempt login before being banned. - # The user is banned if the authenticaction failed `max_retries` times in a `find_time` seconds window. find_time: 120 - - # The length of time before a banned user can login again. ban_time: 300 -# Configuration of the storage backend used to store data and secrets. -# -# You must use only an available configuration: local, mongo storage: - # The directory where the DB files will be saved - ## local: - ## path: /var/lib/authelia/store - - # Settings to connect to mongo server - mongo: - url: mongodb://127.0.0.1 + sql: + host: 127.0.0.1 + port: 3306 database: authelia - auth: - username: authelia - password: authelia + username: authelia + password: authelia -# Configuration of the notification system. -# -# Notifications are sent to users when they require a password reset, a u2f -# registration or a TOTP registration. -# Use only an available configuration: filesystem, gmail notifier: - # For testing purpose, notifications can be sent in a file - ## filesystem: - ## filename: /tmp/authelia/notification.txt - - # Use your email account to send the notifications. You can use an app password. - # List of valid services can be found here: https://nodemailer.com/smtp/well-known/ - ## email: - ## username: user@example.com - ## password: yourpassword - ## sender: admin@example.com - ## service: gmail - - # Use a SMTP server for sending notifications smtp: username: test password: password diff --git a/test/suites/HighAvailability/README.md b/test/suites/HighAvailability/README.md index e2c34e02..0cd3d722 100644 --- a/test/suites/HighAvailability/README.md +++ b/test/suites/HighAvailability/README.md @@ -5,7 +5,7 @@ all components making Authelia highly available. ## Components -This suite will spawn nginx as the edge reverse proxy, redis and mongo for storing +This suite will spawn nginx as the edge reverse proxy, redis and mariadb for storing user sessions and configurations, LDAP for storing user accounts and authenticating, as well as a few helpers such as a fake webmail to receive e-mails sent by Authelia and httpbin to check headers forwarded by Authelia. diff --git a/test/suites/HighAvailability/environment.ts b/test/suites/HighAvailability/environment.ts index 5ae03dd2..28cb8b05 100644 --- a/test/suites/HighAvailability/environment.ts +++ b/test/suites/HighAvailability/environment.ts @@ -2,7 +2,7 @@ const composeFiles = [ 'docker-compose.yml', 'example/compose/authelia/docker-compose.backend.yml', 'example/compose/authelia/docker-compose.frontend.yml', - 'example/compose/mongo/docker-compose.yml', + 'example/compose/mariadb/docker-compose.yml', 'example/compose/redis/docker-compose.yml', 'example/compose/nginx/backend/docker-compose.yml', 'example/compose/nginx/portal/docker-compose.yml', diff --git a/test/suites/HighAvailability/scenarii/MongoConnectionRecovery.ts b/test/suites/HighAvailability/scenarii/MariaConnectionRecovery.ts similarity index 78% rename from test/suites/HighAvailability/scenarii/MongoConnectionRecovery.ts rename to test/suites/HighAvailability/scenarii/MariaConnectionRecovery.ts index b34cc421..f066cae9 100644 --- a/test/suites/HighAvailability/scenarii/MongoConnectionRecovery.ts +++ b/test/suites/HighAvailability/scenarii/MariaConnectionRecovery.ts @@ -4,17 +4,19 @@ import WithDriver from "../../../helpers/context/WithDriver"; import Logout from "../../../helpers/Logout"; import { composeFiles } from '../environment'; import DockerCompose from "../../../helpers/context/DockerCompose"; +import sleep from "../../../helpers/utils/sleep"; export default function () { const dockerCompose = new DockerCompose(composeFiles); WithDriver(); - it.only("should be able to login after mongo restarts", async function () { + it.only("should be able to login after mariadb restarts", async function () { this.timeout(30000); const secret = await LoginAndRegisterTotp(this.driver, "john", "password", true); - await dockerCompose.restart('mongo'); + await dockerCompose.restart('mariadb'); + await sleep(2000); await Logout(this.driver); await FullLogin(this.driver, "john", secret, "https://admin.example.com:8080/secret.html"); diff --git a/test/suites/HighAvailability/test.ts b/test/suites/HighAvailability/test.ts index 68fd859c..f744eb0d 100644 --- a/test/suites/HighAvailability/test.ts +++ b/test/suites/HighAvailability/test.ts @@ -1,5 +1,5 @@ import AutheliaSuite from "../../helpers/context/AutheliaSuite"; -import MongoConnectionRecovery from "./scenarii/MongoConnectionRecovery"; +import MariaConnectionRecovery from "./scenarii/MariaConnectionRecovery"; import EnforceInternalRedirectionsOnly from "./scenarii/EnforceInternalRedirectionsOnly"; import AccessControl from "./scenarii/AccessControl"; import CustomHeadersForwarded from "./scenarii/CustomHeadersForwarded"; @@ -12,7 +12,7 @@ AutheliaSuite(__dirname, function () { describe('Custom headers forwarded to backend', CustomHeadersForwarded); describe('Access control', AccessControl); - describe('Mongo broken connection recovery', MongoConnectionRecovery); + describe('Mariadb broken connection recovery', MariaConnectionRecovery); describe('Enforce internal redirections only', EnforceInternalRedirectionsOnly); describe('Basic authentication', BasicAuthentication); describe('Authelia restart', AutheliaRestart);