[MISC] Implement golint recommendations (#885)

Co-authored-by: Clément Michaud <clement.michaud34@gmail.com>
pull/822/head
Amir Zarrinkafsh 2020-04-21 07:03:38 +10:00 committed by GitHub
parent a6b7a8632b
commit 2e784084c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 111 additions and 70 deletions

View File

@ -1,4 +1,7 @@
package main package main
// BuildTag tag used to bootstrap Authelia binary.
var BuildTag = "__BUILD_TAG__" var BuildTag = "__BUILD_TAG__"
// BuildCommit commit used to bootstrap Authelia binary.
var BuildCommit = "__BUILD_COMMIT__" var BuildCommit = "__BUILD_COMMIT__"

4
go.mod
View File

@ -19,7 +19,7 @@ require (
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kr/pty v1.1.8 // indirect github.com/kr/pty v1.1.8 // indirect
github.com/lib/pq v1.3.0 github.com/lib/pq v1.3.0
github.com/mattn/go-sqlite3 v1.13.0 github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/ogier/pflag v0.0.1 // indirect github.com/ogier/pflag v0.0.1 // indirect
github.com/onsi/ginkgo v1.10.3 // indirect github.com/onsi/ginkgo v1.10.3 // indirect
github.com/onsi/gomega v1.7.1 // indirect github.com/onsi/gomega v1.7.1 // indirect
@ -32,7 +32,7 @@ require (
github.com/stretchr/testify v1.5.1 github.com/stretchr/testify v1.5.1
github.com/tebeka/selenium v0.9.9 github.com/tebeka/selenium v0.9.9
github.com/tstranex/u2f v1.0.0 github.com/tstranex/u2f v1.0.0
github.com/valyala/fasthttp v1.10.0 github.com/valyala/fasthttp v1.11.0
github.com/xdg/stringprep v1.0.0 // indirect github.com/xdg/stringprep v1.0.0 // indirect
go.mongodb.org/mongo-driver v1.3.2 go.mongodb.org/mongo-driver v1.3.2
google.golang.org/appengine v1.6.5 // indirect google.golang.org/appengine v1.6.5 // indirect

3
go.sum
View File

@ -89,6 +89,7 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4= 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-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs=
github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= 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-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
@ -212,6 +213,7 @@ github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c= github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c=
github.com/mattn/go-sqlite3 v1.13.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.13.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@ -447,6 +449,7 @@ golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=

View File

@ -1,23 +1,23 @@
package authentication package authentication
// Level is the type representing a level of authentication // Level is the type representing a level of authentication.
type Level int type Level int
const ( const (
// NotAuthenticated if the user is not authenticated yet // NotAuthenticated if the user is not authenticated yet.
NotAuthenticated Level = iota NotAuthenticated Level = iota
// OneFactor if the user has passed first factor only // OneFactor if the user has passed first factor only.
OneFactor Level = iota OneFactor Level = iota
// TwoFactor if the user has passed two factors // TwoFactor if the user has passed two factors.
TwoFactor Level = iota TwoFactor Level = iota
) )
const ( const (
// TOTP Method using Time-Based One-Time Password applications like Google Authenticator // TOTP Method using Time-Based One-Time Password applications like Google Authenticator.
TOTP = "totp" TOTP = "totp"
// U2F Method using U2F devices like Yubikeys // U2F Method using U2F devices like Yubikeys.
U2F = "u2f" U2F = "u2f"
// Push Method using Duo application to receive push notifications // Push Method using Duo application to receive push notifications.
Push = "mobile_push" Push = "mobile_push"
) )
@ -25,13 +25,14 @@ const (
var PossibleMethods = []string{TOTP, U2F, Push} var PossibleMethods = []string{TOTP, U2F, Push}
const ( const (
//Argon2id Hash Identifier // HashingAlgorithmArgon2id Argon2id hash identifier.
HashingAlgorithmArgon2id = "argon2id" HashingAlgorithmArgon2id = "argon2id"
//SHA512 Hash Identifier // HashingAlgorithmSHA512 SHA512 hash identifier.
HashingAlgorithmSHA512 = "6" HashingAlgorithmSHA512 = "6"
) )
// These are the default values from the upstream crypt module, we use them to for GetInt, and they need to be checked when updating github.com/simia-tech/crypt // These are the default values from the upstream crypt module we use them to for GetInt
// and they need to be checked when updating github.com/simia-tech/crypt.
const ( const (
HashingDefaultArgon2idTime = 1 HashingDefaultArgon2idTime = 1
HashingDefaultArgon2idMemory = 32 * 1024 HashingDefaultArgon2idMemory = 32 * 1024
@ -40,5 +41,5 @@ const (
HashingDefaultSHA512Iterations = 5000 HashingDefaultSHA512Iterations = 5000
) )
// HashingPossibleSaltCharacters represents valid hashing runes // HashingPossibleSaltCharacters represents valid hashing runes.
var HashingPossibleSaltCharacters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/") var HashingPossibleSaltCharacters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/")

View File

@ -17,35 +17,39 @@ type LDAPConnection interface {
Modify(modifyRequest *ldap.ModifyRequest) error Modify(modifyRequest *ldap.ModifyRequest) error
} }
// LDAPConnectionImpl the production implementation of an ldap connection // LDAPConnectionImpl the production implementation of an ldap connection.
type LDAPConnectionImpl struct { type LDAPConnectionImpl struct {
conn *ldap.Conn conn *ldap.Conn
} }
// NewLDAPConnectionImpl create a new ldap connection // NewLDAPConnectionImpl create a new ldap connection.
func NewLDAPConnectionImpl(conn *ldap.Conn) *LDAPConnectionImpl { func NewLDAPConnectionImpl(conn *ldap.Conn) *LDAPConnectionImpl {
return &LDAPConnectionImpl{conn} return &LDAPConnectionImpl{conn}
} }
// Bind binds ldap connection to a username/password.
func (lc *LDAPConnectionImpl) Bind(username, password string) error { func (lc *LDAPConnectionImpl) Bind(username, password string) error {
return lc.conn.Bind(username, password) return lc.conn.Bind(username, password)
} }
// Close closes a ldap connection.
func (lc *LDAPConnectionImpl) Close() { func (lc *LDAPConnectionImpl) Close() {
lc.conn.Close() lc.conn.Close()
} }
// Search searches a ldap server.
func (lc *LDAPConnectionImpl) Search(searchRequest *ldap.SearchRequest) (*ldap.SearchResult, error) { func (lc *LDAPConnectionImpl) Search(searchRequest *ldap.SearchRequest) (*ldap.SearchResult, error) {
return lc.conn.Search(searchRequest) return lc.conn.Search(searchRequest)
} }
// Modify modifies an ldap object.
func (lc *LDAPConnectionImpl) Modify(modifyRequest *ldap.ModifyRequest) error { func (lc *LDAPConnectionImpl) Modify(modifyRequest *ldap.ModifyRequest) error {
return lc.conn.Modify(modifyRequest) return lc.conn.Modify(modifyRequest)
} }
// ********************* FACTORY *********************** // ********************* FACTORY ***********************
// LDAPConnectionFactory an interface of factory of ldap connections // LDAPConnectionFactory an interface of factory of ldap connections.
type LDAPConnectionFactory interface { type LDAPConnectionFactory interface {
DialTLS(network, addr string, config *tls.Config) (LDAPConnection, error) DialTLS(network, addr string, config *tls.Config) (LDAPConnection, error)
Dial(network, addr string) (LDAPConnection, error) Dial(network, addr string) (LDAPConnection, error)
@ -54,7 +58,7 @@ type LDAPConnectionFactory interface {
// LDAPConnectionFactoryImpl the production implementation of an ldap connection factory. // LDAPConnectionFactoryImpl the production implementation of an ldap connection factory.
type LDAPConnectionFactoryImpl struct{} type LDAPConnectionFactoryImpl struct{}
// NewLDAPConnectionFactoryImpl create a concrete ldap connection factory // NewLDAPConnectionFactoryImpl create a concrete ldap connection factory.
func NewLDAPConnectionFactoryImpl() *LDAPConnectionFactoryImpl { func NewLDAPConnectionFactoryImpl() *LDAPConnectionFactoryImpl {
return &LDAPConnectionFactoryImpl{} return &LDAPConnectionFactoryImpl{}
} }

View File

@ -27,6 +27,7 @@ func NewLDAPUserProvider(configuration schema.LDAPAuthenticationBackendConfigura
} }
} }
// NewLDAPUserProviderWithFactory creates a new instance of LDAPUserProvider with existing factory.
func NewLDAPUserProviderWithFactory(configuration schema.LDAPAuthenticationBackendConfiguration, func NewLDAPUserProviderWithFactory(configuration schema.LDAPAuthenticationBackendConfiguration,
connectionFactory LDAPConnectionFactory) *LDAPUserProvider { connectionFactory LDAPConnectionFactory) *LDAPUserProvider {
return &LDAPUserProvider{ return &LDAPUserProvider{
@ -90,7 +91,7 @@ func (p *LDAPUserProvider) CheckUserPassword(inputUsername string, password stri
return true, nil return true, nil
} }
// OWASP recommends to escape some special characters // OWASP recommends to escape some special characters.
// https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/LDAP_Injection_Prevention_Cheat_Sheet.md // https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/LDAP_Injection_Prevention_Cheat_Sheet.md
const specialLDAPRunes = ",#+<>;\"=" const specialLDAPRunes = ",#+<>;\"="
@ -111,7 +112,7 @@ type ldapUserProfile struct {
func (p *LDAPUserProvider) resolveUsersFilter(userFilter string, inputUsername string) string { func (p *LDAPUserProvider) resolveUsersFilter(userFilter string, inputUsername string) string {
inputUsername = p.ldapEscape(inputUsername) inputUsername = p.ldapEscape(inputUsername)
// We temporarily keep placeholder {0} for backward compatibility // We temporarily keep placeholder {0} for backward compatibility.
userFilter = strings.ReplaceAll(userFilter, "{0}", inputUsername) userFilter = strings.ReplaceAll(userFilter, "{0}", inputUsername)
// The {username} placeholder is equivalent to {0}, it's the new way, a named placeholder. // The {username} placeholder is equivalent to {0}, it's the new way, a named placeholder.
@ -137,7 +138,7 @@ func (p *LDAPUserProvider) getUserProfile(conn LDAPConnection, inputUsername str
p.configuration.MailAttribute, p.configuration.MailAttribute,
p.configuration.UsernameAttribute} p.configuration.UsernameAttribute}
// Search for the given username // Search for the given username.
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
1, 0, false, userFilter, attributes, nil, 1, 0, false, userFilter, attributes, nil,
@ -182,11 +183,11 @@ func (p *LDAPUserProvider) getUserProfile(conn LDAPConnection, inputUsername str
func (p *LDAPUserProvider) resolveGroupsFilter(inputUsername string, profile *ldapUserProfile) (string, error) { //nolint:unparam func (p *LDAPUserProvider) resolveGroupsFilter(inputUsername string, profile *ldapUserProfile) (string, error) { //nolint:unparam
inputUsername = p.ldapEscape(inputUsername) inputUsername = p.ldapEscape(inputUsername)
// We temporarily keep placeholder {0} for backward compatibility // We temporarily keep placeholder {0} for backward compatibility.
groupFilter := strings.ReplaceAll(p.configuration.GroupsFilter, "{0}", inputUsername) groupFilter := strings.ReplaceAll(p.configuration.GroupsFilter, "{0}", inputUsername)
groupFilter = strings.ReplaceAll(groupFilter, "{input}", inputUsername) groupFilter = strings.ReplaceAll(groupFilter, "{input}", inputUsername)
if profile != nil { if profile != nil {
// We temporarily keep placeholder {1} for backward compatibility // We temporarily keep placeholder {1} for backward compatibility.
groupFilter = strings.ReplaceAll(groupFilter, "{1}", ldap.EscapeFilter(profile.Username)) groupFilter = strings.ReplaceAll(groupFilter, "{1}", ldap.EscapeFilter(profile.Username))
groupFilter = strings.ReplaceAll(groupFilter, "{username}", ldap.EscapeFilter(profile.Username)) groupFilter = strings.ReplaceAll(groupFilter, "{username}", ldap.EscapeFilter(profile.Username))
groupFilter = strings.ReplaceAll(groupFilter, "{dn}", ldap.EscapeFilter(profile.DN)) groupFilter = strings.ReplaceAll(groupFilter, "{dn}", ldap.EscapeFilter(profile.DN))
@ -219,7 +220,7 @@ func (p *LDAPUserProvider) GetDetails(inputUsername string) (*UserDetails, error
groupBaseDN = p.configuration.AdditionalGroupsDN + "," + groupBaseDN groupBaseDN = p.configuration.AdditionalGroupsDN + "," + groupBaseDN
} }
// Search for the given username // Search for the given username.
searchGroupRequest := ldap.NewSearchRequest( searchGroupRequest := ldap.NewSearchRequest(
groupBaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, groupBaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
0, 0, false, groupsFilter, []string{p.configuration.GroupNameAttribute}, nil, 0, 0, false, groupsFilter, []string{p.configuration.GroupNameAttribute}, nil,
@ -237,7 +238,7 @@ func (p *LDAPUserProvider) GetDetails(inputUsername string) (*UserDetails, error
logging.Logger().Warningf("No groups retrieved from LDAP for user %s", inputUsername) logging.Logger().Warningf("No groups retrieved from LDAP for user %s", inputUsername)
break break
} }
// append all values of the document. Normally there should be only one per document. // Append all values of the document. Normally there should be only one per document.
groups = append(groups, res.Attributes[0].Values...) groups = append(groups, res.Attributes[0].Values...)
} }

View File

@ -82,6 +82,7 @@ func selectMatchingRules(rules []schema.ACLRule, subject Subject, object Object)
return selectMatchingObjectRules(matchingRules, object) return selectMatchingObjectRules(matchingRules, object)
} }
// PolicyToLevel converts a string policy to int authorization level.
func PolicyToLevel(policy string) Level { func PolicyToLevel(policy string) Level {
switch policy { switch policy {
case "bypass": case "bypass":

View File

@ -171,11 +171,13 @@ func generateSelfSignedCertificate(cmd *cobra.Command, args []string) {
log.Printf("wrote %s\n", keyPath) log.Printf("wrote %s\n", keyPath)
} }
// CertificatesCmd certificate helper command.
var CertificatesCmd = &cobra.Command{ var CertificatesCmd = &cobra.Command{
Use: "certificates", Use: "certificates",
Short: "Commands related to certificate generation", Short: "Commands related to certificate generation",
} }
// CertificatesGenerateCmd certificate generation command.
var CertificatesGenerateCmd = &cobra.Command{ var CertificatesGenerateCmd = &cobra.Command{
Use: "generate", Use: "generate",
Short: "Generate a self-signed certificate", Short: "Generate a self-signed certificate",

View File

@ -19,6 +19,7 @@ func init() {
HashPasswordCmd.Flags().IntP("salt-length", "l", schema.DefaultPasswordConfiguration.SaltLength, "set the auto-generated salt length") HashPasswordCmd.Flags().IntP("salt-length", "l", schema.DefaultPasswordConfiguration.SaltLength, "set the auto-generated salt length")
} }
// HashPasswordCmd password hashing command.
var HashPasswordCmd = &cobra.Command{ var HashPasswordCmd = &cobra.Command{
Use: "hash-password [password]", Use: "hash-password [password]",
Short: "Hash a password to be used in file-based users database. Default algorithm is argon2id.", Short: "Hash a password to be used in file-based users database. Default algorithm is argon2id.",

View File

@ -10,6 +10,7 @@ import (
"github.com/authelia/authelia/internal/storage" "github.com/authelia/authelia/internal/storage"
) )
// MigrateCmd migration helper command.
var MigrateCmd *cobra.Command var MigrateCmd *cobra.Command
func init() { func init() {
@ -20,7 +21,7 @@ func init() {
MigrateCmd.AddCommand(MigrateLocalCmd, MigrateMongoCmd) MigrateCmd.AddCommand(MigrateLocalCmd, MigrateMongoCmd)
} }
// TOTPSecretsV3 one entry of TOTP secrets in v3 // TOTPSecretsV3 one entry of TOTP secrets in v3.
type TOTPSecretsV3 struct { type TOTPSecretsV3 struct {
UserID string `json:"userId"` UserID string `json:"userId"`
Secret struct { Secret struct {
@ -28,7 +29,7 @@ type TOTPSecretsV3 struct {
} `json:"secret"` } `json:"secret"`
} }
// U2FDeviceHandleV3 one entry of U2F device handle in v3 // U2FDeviceHandleV3 one entry of U2F device handle in v3.
type U2FDeviceHandleV3 struct { type U2FDeviceHandleV3 struct {
UserID string `json:"userId"` UserID string `json:"userId"`
Registration struct { Registration struct {
@ -37,13 +38,13 @@ type U2FDeviceHandleV3 struct {
} `json:"registration"` } `json:"registration"`
} }
// PreferencesV3 one entry of preferences in v3 // PreferencesV3 one entry of preferences in v3.
type PreferencesV3 struct { type PreferencesV3 struct {
UserID string `json:"userId"` UserID string `json:"userId"`
Method string `json:"method"` Method string `json:"method"`
} }
// AuthenticationTraceV3 one authentication trace in v3 // AuthenticationTraceV3 one authentication trace in v3.
type AuthenticationTraceV3 struct { type AuthenticationTraceV3 struct {
UserID string `json:"userId"` UserID string `json:"userId"`
Successful bool `json:"isAuthenticationSuccessful"` Successful bool `json:"isAuthenticationSuccessful"`

View File

@ -17,7 +17,7 @@ import (
var configurationPath string var configurationPath string
var localDatabasePath string var localDatabasePath string
// MigrateLocalCmd migration command // MigrateLocalCmd migration command.
var MigrateLocalCmd = &cobra.Command{ var MigrateLocalCmd = &cobra.Command{
Use: "localdb", Use: "localdb",
Short: "Migrate data from v3 local database into database configured in v4 configuration file", Short: "Migrate data from v3 local database into database configured in v4 configuration file",
@ -32,7 +32,7 @@ func init() {
MigrateLocalCmd.MarkPersistentFlagRequired("config") MigrateLocalCmd.MarkPersistentFlagRequired("config")
} }
// migrateLocal data from v3 to v4 // migrateLocal data from v3 to v4.
func migrateLocal(cmd *cobra.Command, args []string) { func migrateLocal(cmd *cobra.Command, args []string) {
dbProvider := createDBProvider(configurationPath) dbProvider := createDBProvider(configurationPath)

View File

@ -17,7 +17,7 @@ import (
var mongoURL string var mongoURL string
var mongoDatabase string var mongoDatabase string
// MigrateMongoCmd migration command // MigrateMongoCmd migration command.
var MigrateMongoCmd = &cobra.Command{ var MigrateMongoCmd = &cobra.Command{
Use: "mongo", Use: "mongo",
Short: "Migrate data from v3 mongo database into database configured in v4 configuration file", Short: "Migrate data from v3 mongo database into database configured in v4 configuration file",

View File

@ -6,8 +6,7 @@ import (
"strings" "strings"
) )
// ACLRule represent one ACL rule // ACLRule represent one ACL rule "weak" coerces a single value into string slice.
// "weak" coerces a single value into string slice
type ACLRule struct { type ACLRule struct {
Domains []string `mapstructure:"domain,weak"` Domains []string `mapstructure:"domain,weak"`
Policy string `mapstructure:"policy"` Policy string `mapstructure:"policy"`
@ -16,25 +15,24 @@ type ACLRule struct {
Resources []string `mapstructure:"resources"` Resources []string `mapstructure:"resources"`
} }
// IsPolicyValid check if policy is valid // IsPolicyValid check if policy is valid.
func IsPolicyValid(policy string) bool { func IsPolicyValid(policy string) bool {
return policy == "deny" || policy == "one_factor" || policy == "two_factor" || policy == "bypass" return policy == "deny" || policy == "one_factor" || policy == "two_factor" || policy == "bypass"
} }
// IsSubjectValid check if a subject is valid // IsSubjectValid check if a subject is valid.
func IsSubjectValid(subject string) bool { func IsSubjectValid(subject string) bool {
return subject == "" || strings.HasPrefix(subject, "user:") || strings.HasPrefix(subject, "group:") return subject == "" || strings.HasPrefix(subject, "user:") || strings.HasPrefix(subject, "group:")
} }
// IsNetworkValid check if a network is valid // IsNetworkValid check if a network is valid.
func IsNetworkValid(network string) bool { func IsNetworkValid(network string) bool {
_, _, err := net.ParseCIDR(network) _, _, err := net.ParseCIDR(network)
return err == nil return err == nil
} }
// Validate validate an ACL Rule // Validate validate an ACL Rule.
func (r *ACLRule) Validate(validator *StructValidator) { func (r *ACLRule) Validate(validator *StructValidator) {
if len(r.Domains) == 0 { if len(r.Domains) == 0 {
validator.Push(fmt.Errorf("Domain must be provided")) validator.Push(fmt.Errorf("Domain must be provided"))
} }
@ -62,7 +60,7 @@ type AccessControlConfiguration struct {
Rules []ACLRule `mapstructure:"rules"` Rules []ACLRule `mapstructure:"rules"`
} }
// Validate validate the access control configuration // Validate validate the access control configuration.
func (acc *AccessControlConfiguration) Validate(validator *StructValidator) { func (acc *AccessControlConfiguration) Validate(validator *StructValidator) {
if acc.DefaultPolicy == "" { if acc.DefaultPolicy == "" {
acc.DefaultPolicy = "deny" acc.DefaultPolicy = "deny"

View File

@ -1,6 +1,6 @@
package schema package schema
// LDAPAuthenticationBackendConfiguration represents the configuration related to LDAP server // LDAPAuthenticationBackendConfiguration represents the configuration related to LDAP server.
type LDAPAuthenticationBackendConfiguration struct { type LDAPAuthenticationBackendConfiguration struct {
URL string `mapstructure:"url"` URL string `mapstructure:"url"`
SkipVerify bool `mapstructure:"skip_verify"` SkipVerify bool `mapstructure:"skip_verify"`
@ -16,12 +16,13 @@ type LDAPAuthenticationBackendConfiguration struct {
Password string `mapstructure:"password"` Password string `mapstructure:"password"`
} }
// FileAuthenticationBackendConfiguration represents the configuration related to file-based backend // FileAuthenticationBackendConfiguration represents the configuration related to file-based backend.
type FileAuthenticationBackendConfiguration struct { type FileAuthenticationBackendConfiguration struct {
Path string `mapstructure:"path"` Path string `mapstructure:"path"`
Password *PasswordConfiguration `mapstructure:"password"` Password *PasswordConfiguration `mapstructure:"password"`
} }
// PasswordConfiguration represents the configuration related to password hashing.
type PasswordConfiguration struct { type PasswordConfiguration struct {
Iterations int `mapstructure:"iterations"` Iterations int `mapstructure:"iterations"`
KeyLength int `mapstructure:"key_length"` KeyLength int `mapstructure:"key_length"`
@ -31,7 +32,7 @@ type PasswordConfiguration struct {
Parallelism int `mapstructure:"parallelism"` Parallelism int `mapstructure:"parallelism"`
} }
// DefaultPasswordConfiguration represents the default configuration related to Argon2id hashing // DefaultPasswordConfiguration represents the default configuration related to Argon2id hashing.
var DefaultPasswordConfiguration = PasswordConfiguration{ var DefaultPasswordConfiguration = PasswordConfiguration{
Iterations: 1, Iterations: 1,
KeyLength: 32, KeyLength: 32,
@ -41,7 +42,7 @@ var DefaultPasswordConfiguration = PasswordConfiguration{
Parallelism: 8, Parallelism: 8,
} }
// DefaultCIPasswordConfiguration represents the default configuration related to Argon2id hashing for CI // DefaultCIPasswordConfiguration represents the default configuration related to Argon2id hashing for CI.
var DefaultCIPasswordConfiguration = PasswordConfiguration{ var DefaultCIPasswordConfiguration = PasswordConfiguration{
Iterations: 1, Iterations: 1,
KeyLength: 32, KeyLength: 32,
@ -51,14 +52,14 @@ var DefaultCIPasswordConfiguration = PasswordConfiguration{
Parallelism: 8, Parallelism: 8,
} }
// DefaultPasswordSHA512Configuration represents the default configuration related to SHA512 hashing // DefaultPasswordSHA512Configuration represents the default configuration related to SHA512 hashing.
var DefaultPasswordSHA512Configuration = PasswordConfiguration{ var DefaultPasswordSHA512Configuration = PasswordConfiguration{
Iterations: 50000, Iterations: 50000,
SaltLength: 16, SaltLength: 16,
Algorithm: "sha512", Algorithm: "sha512",
} }
// AuthenticationBackendConfiguration represents the configuration related to the authentication backend // AuthenticationBackendConfiguration represents the configuration related to the authentication backend.
type AuthenticationBackendConfiguration struct { type AuthenticationBackendConfiguration struct {
DisableResetPassword bool `mapstructure:"disable_reset_password"` DisableResetPassword bool `mapstructure:"disable_reset_password"`
Ldap *LDAPAuthenticationBackendConfiguration `mapstructure:"ldap"` Ldap *LDAPAuthenticationBackendConfiguration `mapstructure:"ldap"`

View File

@ -24,6 +24,7 @@ type NotifierConfiguration struct {
SMTP *SMTPNotifierConfiguration `mapstructure:"smtp"` SMTP *SMTPNotifierConfiguration `mapstructure:"smtp"`
} }
// DefaultSMTPNotifierConfiguration represents default configuration parameters for the SMTP notifier.
var DefaultSMTPNotifierConfiguration = SMTPNotifierConfiguration{ var DefaultSMTPNotifierConfiguration = SMTPNotifierConfiguration{
Subject: "[Authelia] {title}", Subject: "[Authelia] {title}",
} }

View File

@ -7,6 +7,7 @@ type RegulationConfiguration struct {
BanTime string `mapstructure:"ban_time"` BanTime string `mapstructure:"ban_time"`
} }
// DefaultRegulationConfiguration represents default configuration parameters for the regulator.
var DefaultRegulationConfiguration = RegulationConfiguration{ var DefaultRegulationConfiguration = RegulationConfiguration{
MaxRetries: 3, MaxRetries: 3,
FindTime: "2m", FindTime: "2m",

View File

@ -8,6 +8,8 @@ type TOTPConfiguration struct {
} }
var defaultOtpSkew = 1 var defaultOtpSkew = 1
// DefaultTOTPConfiguration represents default configuration parameters for TOTP generation.
var DefaultTOTPConfiguration = TOTPConfiguration{ var DefaultTOTPConfiguration = TOTPConfiguration{
Issuer: "Authelia", Issuer: "Authelia",
Period: 30, Period: 30,

View File

@ -6,7 +6,7 @@ import (
"github.com/authelia/authelia/internal/configuration/schema" "github.com/authelia/authelia/internal/configuration/schema"
) )
// ValidateSession validates and update session configuration. // ValidateNotifier validates and update notifier configuration.
func ValidateNotifier(configuration *schema.NotifierConfiguration, validator *schema.StructValidator) { func ValidateNotifier(configuration *schema.NotifierConfiguration, validator *schema.StructValidator) {
if configuration.SMTP == nil && configuration.FileSystem == nil { if configuration.SMTP == nil && configuration.FileSystem == nil {
validator.Push(fmt.Errorf("Notifier should be either `smtp` or `filesystem`")) validator.Push(fmt.Errorf("Notifier should be either `smtp` or `filesystem`"))

View File

@ -7,7 +7,7 @@ import (
"github.com/authelia/authelia/internal/utils" "github.com/authelia/authelia/internal/utils"
) )
// ValidateSession validates and update session configuration. // ValidateRegulation validates and update regulator configuration.
func ValidateRegulation(configuration *schema.RegulationConfiguration, validator *schema.StructValidator) { func ValidateRegulation(configuration *schema.RegulationConfiguration, validator *schema.StructValidator) {
if configuration.FindTime == "" { if configuration.FindTime == "" {
configuration.FindTime = schema.DefaultRegulationConfiguration.FindTime // 2 min configuration.FindTime = schema.DefaultRegulationConfiguration.FindTime // 2 min

View File

@ -6,7 +6,7 @@ import (
"github.com/authelia/authelia/internal/configuration/schema" "github.com/authelia/authelia/internal/configuration/schema"
) )
// ValidateSQLStorage validates storage configuration. // ValidateStorage validates storage configuration.
func ValidateStorage(configuration schema.StorageConfiguration, validator *schema.StructValidator) { func ValidateStorage(configuration schema.StorageConfiguration, validator *schema.StructValidator) {
if configuration.Local == nil && configuration.MySQL == nil && configuration.PostgreSQL == nil { if configuration.Local == nil && configuration.MySQL == nil && configuration.PostgreSQL == nil {
validator.Push(errors.New("A storage configuration must be provided. It could be 'local', 'mysql' or 'postgres'")) validator.Push(errors.New("A storage configuration must be provided. It could be 'local', 'mysql' or 'postgres'"))

View File

@ -11,6 +11,7 @@ const ResetPasswordAction = "ResetPassword"
const authPrefix = "Basic " const authPrefix = "Basic "
// AuthorizationHeader is the basic-auth HTTP header Authelia utilises.
const AuthorizationHeader = "Proxy-Authorization" const AuthorizationHeader = "Proxy-Authorization"
const remoteUserHeader = "Remote-User" const remoteUserHeader = "Remote-User"
const remoteGroupsHeader = "Remote-Groups" const remoteGroupsHeader = "Remote-Groups"
@ -18,7 +19,7 @@ const remoteGroupsHeader = "Remote-Groups"
var protoHostSeparator = []byte("://") var protoHostSeparator = []byte("://")
const ( const (
// Forbidden means the user is forbidden the access to a resource // Forbidden means the user is forbidden the access to a resource.
Forbidden authorizationMatching = iota Forbidden authorizationMatching = iota
// NotAuthorized means the user can access the resource with more permissions. // NotAuthorized means the user can access the resource with more permissions.
NotAuthorized authorizationMatching = iota NotAuthorized authorizationMatching = iota

View File

@ -2,12 +2,14 @@ package handlers
import "github.com/authelia/authelia/internal/middlewares" import "github.com/authelia/authelia/internal/middlewares"
// ConfigurationBody configuration parameters exposed to the frontend.
type ConfigurationBody struct { type ConfigurationBody struct {
GoogleAnalyticsTrackingID string `json:"ga_tracking_id,omitempty"` GoogleAnalyticsTrackingID string `json:"ga_tracking_id,omitempty"`
RememberMe bool `json:"remember_me"` // whether remember me is enabled or not RememberMe bool `json:"remember_me"` // whether remember me is enabled or not
ResetPassword bool `json:"reset_password"` ResetPassword bool `json:"reset_password"`
} }
// ConfigurationGet fetches configuration parameters for frontend mutation.
func ConfigurationGet(ctx *middlewares.AutheliaCtx) { func ConfigurationGet(ctx *middlewares.AutheliaCtx) {
body := ConfigurationBody{ body := ConfigurationBody{
GoogleAnalyticsTrackingID: ctx.Configuration.GoogleAnalyticsTrackingID, GoogleAnalyticsTrackingID: ctx.Configuration.GoogleAnalyticsTrackingID,

View File

@ -79,6 +79,7 @@ func UserInfoGet(ctx *middlewares.AutheliaCtx) {
ctx.SetJSONBody(preferences) ctx.SetJSONBody(preferences)
} }
// MethodBody the selected 2FA method.
type MethodBody struct { type MethodBody struct {
Method string `json:"method" valid:"required"` Method string `json:"method" valid:"required"`
} }

View File

@ -7,15 +7,18 @@ import (
"github.com/pquerna/otp/totp" "github.com/pquerna/otp/totp"
) )
// TOTPVerifier is the interface for verifying TOTPs.
type TOTPVerifier interface { type TOTPVerifier interface {
Verify(token, secret string) (bool, error) Verify(token, secret string) (bool, error)
} }
// TOTPVerifierImpl the production implementation for TOTP verification.
type TOTPVerifierImpl struct { type TOTPVerifierImpl struct {
Period uint Period uint
Skew uint Skew uint
} }
// Verify verifies TOTPs.
func (tv *TOTPVerifierImpl) Verify(token, secret string) (bool, error) { func (tv *TOTPVerifierImpl) Verify(token, secret string) (bool, error) {
opts := totp.ValidateOpts{ opts := totp.ValidateOpts{
Period: tv.Period, Period: tv.Period,

View File

@ -11,15 +11,15 @@ type MethodList = []string
type authorizationMatching int type authorizationMatching int
// UserInfo is the model of user second factor preferences // UserInfo is the model of user second factor preferences.
type UserPreferences struct { type UserPreferences struct {
// The preferred 2FA method. // The preferred 2FA method.
Method string `json:"method" valid:"required"` Method string `json:"method" valid:"required"`
// True if a security key has been registered // True if a security key has been registered.
HasU2F bool `json:"has_u2f" valid:"required"` HasU2F bool `json:"has_u2f" valid:"required"`
// True if a TOTP device has been registered // True if a TOTP device has been registered.
HasTOTP bool `json:"has_totp" valid:"required"` HasTOTP bool `json:"has_totp" valid:"required"`
} }
@ -68,12 +68,12 @@ type StateResponse struct {
DefaultRedirectionURL string `json:"default_redirection_url"` DefaultRedirectionURL string `json:"default_redirection_url"`
} }
// resetPasswordStep1RequestBody model of the reset password (step1) request body // resetPasswordStep1RequestBody model of the reset password (step1) request body.
type resetPasswordStep1RequestBody struct { type resetPasswordStep1RequestBody struct {
Username string `json:"username"` Username string `json:"username"`
} }
// resetPasswordStep2RequestBody model of the reset password (step2) request body // resetPasswordStep2RequestBody model of the reset password (step2) request body.
type resetPasswordStep2RequestBody struct { type resetPasswordStep2RequestBody struct {
Password string `json:"password"` Password string `json:"password"`
} }

View File

@ -6,12 +6,15 @@ import (
"github.com/tstranex/u2f" "github.com/tstranex/u2f"
) )
// U2FVerifier is the interface for verifying U2F keys.
type U2FVerifier interface { type U2FVerifier interface {
Verify(keyHandle []byte, publicKey []byte, signResponse u2f.SignResponse, challenge u2f.Challenge) error Verify(keyHandle []byte, publicKey []byte, signResponse u2f.SignResponse, challenge u2f.Challenge) error
} }
// U2FVerifierImpl the production implementation for U2F key verification.
type U2FVerifierImpl struct{} type U2FVerifierImpl struct{}
// Verify verifies U2F keys.
func (uv *U2FVerifierImpl) Verify(keyHandle []byte, publicKey []byte, func (uv *U2FVerifierImpl) Verify(keyHandle []byte, publicKey []byte,
signResponse u2f.SignResponse, challenge u2f.Challenge) error { signResponse u2f.SignResponse, challenge u2f.Challenge) error {
var registration u2f.Registration var registration u2f.Registration

View File

@ -23,14 +23,14 @@ import (
"github.com/authelia/authelia/internal/session" "github.com/authelia/authelia/internal/session"
) )
// MockAutheliaCtx a mock of AutheliaCtx // MockAutheliaCtx a mock of AutheliaCtx.
type MockAutheliaCtx struct { type MockAutheliaCtx struct {
// Logger hook // Logger hook.
Hook *test.Hook Hook *test.Hook
Ctx *middlewares.AutheliaCtx Ctx *middlewares.AutheliaCtx
Ctrl *gomock.Controller Ctrl *gomock.Controller
// Providers // Providers.
UserProviderMock *MockUserProvider UserProviderMock *MockUserProvider
StorageProviderMock *storage.MockProvider StorageProviderMock *storage.MockProvider
NotifierMock *MockNotifier NotifierMock *MockNotifier
@ -40,27 +40,27 @@ type MockAutheliaCtx struct {
Clock TestingClock Clock TestingClock
} }
// TestingClock implementation of clock for tests // TestingClock implementation of clock for tests.
type TestingClock struct { type TestingClock struct {
now time.Time now time.Time
} }
// Now return the stored clock // Now return the stored clock.
func (dc *TestingClock) Now() time.Time { func (dc *TestingClock) Now() time.Time {
return dc.now return dc.now
} }
// After return a channel receiving the time after duration has elapsed // After return a channel receiving the time after duration has elapsed.
func (dc *TestingClock) After(d time.Duration) <-chan time.Time { func (dc *TestingClock) After(d time.Duration) <-chan time.Time {
return time.After(d) return time.After(d)
} }
// Set set the time of the clock // Set set the time of the clock.
func (dc *TestingClock) Set(now time.Time) { func (dc *TestingClock) Set(now time.Time) {
dc.now = now dc.now = now
} }
// NewMockAutheliaCtx create an instance of AutheliaCtx mock // NewMockAutheliaCtx create an instance of AutheliaCtx mock.
func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx { func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {
mockAuthelia := new(MockAutheliaCtx) mockAuthelia := new(MockAutheliaCtx)
mockAuthelia.Clock = TestingClock{} mockAuthelia.Clock = TestingClock{}
@ -107,7 +107,7 @@ func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {
providers.Regulator = regulation.NewRegulator(configuration.Regulation, providers.StorageProvider, &mockAuthelia.Clock) providers.Regulator = regulation.NewRegulator(configuration.Regulation, providers.StorageProvider, &mockAuthelia.Clock)
request := &fasthttp.RequestCtx{} request := &fasthttp.RequestCtx{}
// Set a cookie to identify this client throughout the test // Set a cookie to identify this client throughout the test.
// request.Request.Header.SetCookie("authelia_session", "client_cookie") // request.Request.Header.SetCookie("authelia_session", "client_cookie")
autheliaCtx, _ := middlewares.NewAutheliaCtx(request, configuration, providers) autheliaCtx, _ := middlewares.NewAutheliaCtx(request, configuration, providers)
@ -120,7 +120,7 @@ func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {
return mockAuthelia return mockAuthelia
} }
// Close close the mock // Close close the mock.
func (m *MockAutheliaCtx) Close() { func (m *MockAutheliaCtx) Close() {
m.Hook.Reset() m.Hook.Reset()
m.Ctrl.Finish() m.Ctrl.Finish()
@ -146,6 +146,7 @@ func (m *MockAutheliaCtx) Assert200OK(t *testing.T, data interface{}) {
assert.Equal(t, string(b), string(m.Ctx.Response.Body())) assert.Equal(t, string(b), string(m.Ctx.Response.Body()))
} }
// GetResponseData retrieves a response from the service.
func (m *MockAutheliaCtx) GetResponseData(t *testing.T, data interface{}) { func (m *MockAutheliaCtx) GetResponseData(t *testing.T, data interface{}) {
okResponse := middlewares.OKResponse{} okResponse := middlewares.OKResponse{}
okResponse.Data = data okResponse.Data = data

View File

@ -2,7 +2,7 @@ package models
import "time" import "time"
// Attempt represent an authentication attempt. // AuthenticationAttempt represent an authentication attempt.
type AuthenticationAttempt struct { type AuthenticationAttempt struct {
// The user who tried to authenticate. // The user who tried to authenticate.
Username string Username string

View File

@ -6,12 +6,21 @@ import (
"time" "time"
) )
// ErrTimeoutReached error thrown when a timeout is reached // ErrTimeoutReached error thrown when a timeout is reached.
var ErrTimeoutReached = errors.New("timeout reached") var ErrTimeoutReached = errors.New("timeout reached")
var parseDurationRegexp = regexp.MustCompile(`^(?P<Duration>[1-9]\d*?)(?P<Unit>[smhdwMy])?$`) var parseDurationRegexp = regexp.MustCompile(`^(?P<Duration>[1-9]\d*?)(?P<Unit>[smhdwMy])?$`)
// Hour is an int based representation of the time unit
const Hour = time.Minute * 60 const Hour = time.Minute * 60
// Day is an int based representation of the time unit
const Day = Hour * 24 const Day = Hour * 24
// Week is an int based representation of the time unit
const Week = Day * 7 const Week = Day * 7
// Year is an int based representation of the time unit
const Year = Day * 365 const Year = Day * 365
// Month is an int based representation of the time unit
const Month = Year / 12 const Month = Year / 12

View File

@ -5,6 +5,7 @@ import (
"strings" "strings"
) )
// IsRedirectionSafe determines if a redirection URL is secured.
func IsRedirectionSafe(url url.URL, protectedDomain string) bool { func IsRedirectionSafe(url url.URL, protectedDomain string) bool {
if url.Scheme != "https" { if url.Scheme != "https" {
return false return false

View File

@ -5,7 +5,7 @@ import (
"time" "time"
) )
// Checks if a single string is in an array of strings // IsStringInSlice checks if a single string is in an array of strings.
func IsStringInSlice(a string, list []string) (inSlice bool) { func IsStringInSlice(a string, list []string) (inSlice bool) {
for _, b := range list { for _, b := range list {
if b == a { if b == a {
@ -15,8 +15,8 @@ func IsStringInSlice(a string, list []string) (inSlice bool) {
return false return false
} }
// Splits a string s into an array with each item being a max of int d // SliceString splits a string s into an array with each item being a max of int d
// d = denominator, n = numerator, q = quotient, r = remainder // d = denominator, n = numerator, q = quotient, r = remainder.
func SliceString(s string, d int) (array []string) { func SliceString(s string, d int) (array []string) {
n := len(s) n := len(s)
q := n / d q := n / d
@ -30,7 +30,7 @@ func SliceString(s string, d int) (array []string) {
return return
} }
// RandomString generate a random string of n characters // RandomString generate a random string of n characters.
func RandomString(n int, characters []rune) (randomString string) { func RandomString(n int, characters []rune) (randomString string) {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
b := make([]rune, n) b := make([]rune, n)