mirror of https://github.com/h44z/wg-portal.git
				
				
				
			
						commit
						88278bf677
					
				|  | @ -130,7 +130,7 @@ The following configuration options are available: | ||||||
| | EMAIL_CERT_VALIDATION | certcheck         | email       | false                                           | Validate the email server certificate.                                                                                               | | | EMAIL_CERT_VALIDATION | certcheck         | email       | false                                           | Validate the email server certificate.                                                                                               | | ||||||
| | EMAIL_USERNAME        | user              | email       |                                                 | An optional username for SMTP authentication.                                                                                        | | | EMAIL_USERNAME        | user              | email       |                                                 | An optional username for SMTP authentication.                                                                                        | | ||||||
| | EMAIL_PASSWORD        | pass              | email       |                                                 | An optional password for SMTP authentication.                                                                                        | | | EMAIL_PASSWORD        | pass              | email       |                                                 | An optional password for SMTP authentication.                                                                                        | | ||||||
| | EMAIL_AUTHTYPE        | auth              | email       | plain                                           | Either plain, login or crammd5. If username and password are empty, this value is ignored.                                                                                                        | | | EMAIL_AUTHTYPE        | auth              | email       | plain                                           | Either plain, login or crammd5. If username and password are empty, this value is ignored.                                           | | ||||||
| | WG_DEVICES            | devices           | wg          | wg0                                             | A comma separated list of WireGuard devices.                                                                                         | | | WG_DEVICES            | devices           | wg          | wg0                                             | A comma separated list of WireGuard devices.                                                                                         | | ||||||
| | WG_DEFAULT_DEVICE     | defaultDevice     | wg          | wg0                                             | This device is used for auto-created peers (if CREATE_DEFAULT_PEER is enabled).                                                      | | | WG_DEFAULT_DEVICE     | defaultDevice     | wg          | wg0                                             | This device is used for auto-created peers (if CREATE_DEFAULT_PEER is enabled).                                                      | | ||||||
| | WG_CONFIG_PATH        | configDirectory   | wg          | /etc/wireguard                                  | If set, interface configuration updates will be written to this path, filename: <devicename>.conf.                                   | | | WG_CONFIG_PATH        | configDirectory   | wg          | /etc/wireguard                                  | If set, interface configuration updates will be written to this path, filename: <devicename>.conf.                                   | | ||||||
|  | @ -141,15 +141,14 @@ The following configuration options are available: | ||||||
| | LDAP_BASEDN           | dn                | ldap        | DC=COMPANY,DC=LOCAL                             | The base DN for searching users.                                                                                                     | | | LDAP_BASEDN           | dn                | ldap        | DC=COMPANY,DC=LOCAL                             | The base DN for searching users.                                                                                                     | | ||||||
| | LDAP_USER             | user              | ldap        | company\\\\ldap_wireguard                       | The bind user.                                                                                                                       | | | LDAP_USER             | user              | ldap        | company\\\\ldap_wireguard                       | The bind user.                                                                                                                       | | ||||||
| | LDAP_PASSWORD         | pass              | ldap        | SuperSecret                                     | The bind password.                                                                                                                   | | | LDAP_PASSWORD         | pass              | ldap        | SuperSecret                                     | The bind password.                                                                                                                   | | ||||||
| | LDAP_TYPE             | typ               | ldap        | AD                                              | Either AD or OpenLDAP.                                                                                                               | | | LDAP_LOGIN_FILTER     | loginFilter       | ldap        | (&(objectClass=organizationalPerson)(mail={{login_identifier}})(!userAccountControl:1.2.840.113556.1.4.803:=2)) | {{login_identifier}} will be replaced with the login email address.  | | ||||||
| | LDAP_USER_CLASS       | userClass         | ldap        | organizationalPerson                            | The user class that specifies the LDAP object category of users.                                                                     | | | LDAP_SYNC_FILTER      | syncFilter        | ldap        | (&(objectClass=organizationalPerson)(!userAccountControl:1.2.840.113556.1.4.803:=2))                            | The filter string for the LDAP synchronization service.              | | ||||||
| | LDAP_ADMIN_GROUP      | adminGroup        | ldap        | CN=WireGuardAdmins,OU=_O_IT,DC=COMPANY,DC=LOCAL | Users in this group are marked as administrators.                                                                                    | | | LDAP_ADMIN_GROUP      | adminGroup        | ldap        | CN=WireGuardAdmins,OU=_O_IT,DC=COMPANY,DC=LOCAL | Users in this group are marked as administrators.                                                                                    | | ||||||
| | LDAP_ATTR_EMAIL       | attrEmail         | ldap        | mail                                            | User email attribute.                                                                                                                | | | LDAP_ATTR_EMAIL       | attrEmail         | ldap        | mail                                            | User email attribute.                                                                                                                | | ||||||
| | LDAP_ATTR_FIRSTNAME   | attrFirstname     | ldap        | givenName                                       | User firstname attribute.                                                                                                            | | | LDAP_ATTR_FIRSTNAME   | attrFirstname     | ldap        | givenName                                       | User firstname attribute.                                                                                                            | | ||||||
| | LDAP_ATTR_LASTNAME    | attrLastname      | ldap        | sn                                              | User lastname attribute.                                                                                                             | | | LDAP_ATTR_LASTNAME    | attrLastname      | ldap        | sn                                              | User lastname attribute.                                                                                                             | | ||||||
| | LDAP_ATTR_PHONE       | attrPhone         | ldap        | telephoneNumber                                 | User phone number attribute.                                                                                                         | | | LDAP_ATTR_PHONE       | attrPhone         | ldap        | telephoneNumber                                 | User phone number attribute.                                                                                                         | | ||||||
| | LDAP_ATTR_GROUPS      | attrGroups        | ldap        | memberOf                                        | User groups attribute.                                                                                                               | | | LDAP_ATTR_GROUPS      | attrGroups        | ldap        | memberOf                                        | User groups attribute.                                                                                                               | | ||||||
| | LDAP_ATTR_DISABLED    | attrDisabled      | ldap        | userAccountControl                              | User status attribute. This attribute is used to detect deactivated users.                                                           | |  | ||||||
| | LOG_LEVEL             |                   |             | debug                                           | Specify log level, one of: trace, debug, info, off.                                                                                  | | | LOG_LEVEL             |                   |             | debug                                           | Specify log level, one of: trace, debug, info, off.                                                                                  | | ||||||
| | LOG_JSON              |                   |             | false                                           | Format log output as JSON.                                                                                                           | | | LOG_JSON              |                   |             | false                                           | Format log output as JSON.                                                                                                           | | ||||||
| | LOG_COLOR             |                   |             | true                                            | Colorize log output.                                                                                                                 | | | LOG_COLOR             |                   |             | true                                            | Colorize log output.                                                                                                                 | | ||||||
|  | @ -174,7 +173,6 @@ ldap: | ||||||
|   user: wireguard@test.test |   user: wireguard@test.test | ||||||
|   pass: test |   pass: test | ||||||
|   adminGroup: CN=WireGuardAdmins,CN=Users,DC=test,DC=test |   adminGroup: CN=WireGuardAdmins,CN=Users,DC=test,DC=test | ||||||
|   typ: AD |  | ||||||
| database: | database: | ||||||
|   typ: sqlite |   typ: sqlite | ||||||
|   database: data/wg_portal.db |   database: data/wg_portal.db | ||||||
|  |  | ||||||
|  | @ -2,7 +2,6 @@ package ldap | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"crypto/tls" | 	"crypto/tls" | ||||||
| 	"fmt" |  | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/gin-gonic/gin" | 	"github.com/gin-gonic/gin" | ||||||
|  | @ -69,13 +68,11 @@ func (provider Provider) Login(ctx *authentication.AuthContext) (string, error) | ||||||
| 
 | 
 | ||||||
| 	// Search for the given username
 | 	// Search for the given username
 | ||||||
| 	attrs := []string{"dn", provider.config.EmailAttribute} | 	attrs := []string{"dn", provider.config.EmailAttribute} | ||||||
| 	if provider.config.DisabledAttribute != "" { | 	loginFilter := strings.Replace(provider.config.LoginFilter, "{{login_identifier}}", username, -1) | ||||||
| 		attrs = append(attrs, provider.config.DisabledAttribute) |  | ||||||
| 	} |  | ||||||
| 	searchRequest := ldap.NewSearchRequest( | 	searchRequest := ldap.NewSearchRequest( | ||||||
| 		provider.config.BaseDN, | 		provider.config.BaseDN, | ||||||
| 		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, | 		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, | ||||||
| 		fmt.Sprintf("(&(objectClass=%s)(%s=%s))", provider.config.UserClass, provider.config.EmailAttribute, username), | 		loginFilter, | ||||||
| 		attrs, | 		attrs, | ||||||
| 		nil, | 		nil, | ||||||
| 	) | 	) | ||||||
|  | @ -89,24 +86,8 @@ func (provider Provider) Login(ctx *authentication.AuthContext) (string, error) | ||||||
| 		return "", errors.Errorf("invalid amount of ldap entries (%d)", len(sr.Entries)) | 		return "", errors.Errorf("invalid amount of ldap entries (%d)", len(sr.Entries)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	userDN := sr.Entries[0].DN |  | ||||||
| 
 |  | ||||||
| 	// Check if user is disabled, if so deny login
 |  | ||||||
| 	if provider.config.DisabledAttribute != "" { |  | ||||||
| 		uac := sr.Entries[0].GetAttributeValue(provider.config.DisabledAttribute) |  | ||||||
| 		switch provider.config.Type { |  | ||||||
| 		case ldapconfig.TypeActiveDirectory: |  | ||||||
| 			if ldapconfig.IsActiveDirectoryUserDisabled(uac) { |  | ||||||
| 				return "", errors.New("user is disabled") |  | ||||||
| 			} |  | ||||||
| 		case ldapconfig.TypeOpenLDAP: |  | ||||||
| 			if ldapconfig.IsOpenLdapUserDisabled(uac) { |  | ||||||
| 				return "", errors.New("user is disabled") |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Bind as the user to verify their password
 | 	// Bind as the user to verify their password
 | ||||||
|  | 	userDN := sr.Entries[0].DN | ||||||
| 	err = client.Bind(userDN, password) | 	err = client.Bind(userDN, password) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", errors.Wrapf(err, "invalid credentials") | 		return "", errors.Wrapf(err, "invalid credentials") | ||||||
|  | @ -136,13 +117,11 @@ func (provider Provider) GetUserModel(ctx *authentication.AuthContext) (*authent | ||||||
| 	// Search for the given username
 | 	// Search for the given username
 | ||||||
| 	attrs := []string{"dn", provider.config.EmailAttribute, provider.config.FirstNameAttribute, provider.config.LastNameAttribute, | 	attrs := []string{"dn", provider.config.EmailAttribute, provider.config.FirstNameAttribute, provider.config.LastNameAttribute, | ||||||
| 		provider.config.PhoneAttribute, provider.config.GroupMemberAttribute} | 		provider.config.PhoneAttribute, provider.config.GroupMemberAttribute} | ||||||
| 	if provider.config.DisabledAttribute != "" { | 	loginFilter := strings.Replace(provider.config.LoginFilter, "{{login_identifier}}", username, -1) | ||||||
| 		attrs = append(attrs, provider.config.DisabledAttribute) |  | ||||||
| 	} |  | ||||||
| 	searchRequest := ldap.NewSearchRequest( | 	searchRequest := ldap.NewSearchRequest( | ||||||
| 		provider.config.BaseDN, | 		provider.config.BaseDN, | ||||||
| 		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, | 		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, | ||||||
| 		fmt.Sprintf("(&(objectClass=%s)(%s=%s))", provider.config.UserClass, provider.config.EmailAttribute, username), | 		loginFilter, | ||||||
| 		attrs, | 		attrs, | ||||||
| 		nil, | 		nil, | ||||||
| 	) | 	) | ||||||
|  |  | ||||||
|  | @ -15,14 +15,13 @@ type Config struct { | ||||||
| 	BindUser       string `yaml:"user" envconfig:"LDAP_USER"` | 	BindUser       string `yaml:"user" envconfig:"LDAP_USER"` | ||||||
| 	BindPass       string `yaml:"pass" envconfig:"LDAP_PASSWORD"` | 	BindPass       string `yaml:"pass" envconfig:"LDAP_PASSWORD"` | ||||||
| 
 | 
 | ||||||
| 	Type                 Type   `yaml:"typ" envconfig:"LDAP_TYPE"` // AD for active directory, OpenLDAP for OpenLDAP
 |  | ||||||
| 	UserClass            string `yaml:"userClass" envconfig:"LDAP_USER_CLASS"` |  | ||||||
| 	EmailAttribute       string `yaml:"attrEmail" envconfig:"LDAP_ATTR_EMAIL"` | 	EmailAttribute       string `yaml:"attrEmail" envconfig:"LDAP_ATTR_EMAIL"` | ||||||
| 	FirstNameAttribute   string `yaml:"attrFirstname" envconfig:"LDAP_ATTR_FIRSTNAME"` | 	FirstNameAttribute   string `yaml:"attrFirstname" envconfig:"LDAP_ATTR_FIRSTNAME"` | ||||||
| 	LastNameAttribute    string `yaml:"attrLastname" envconfig:"LDAP_ATTR_LASTNAME"` | 	LastNameAttribute    string `yaml:"attrLastname" envconfig:"LDAP_ATTR_LASTNAME"` | ||||||
| 	PhoneAttribute       string `yaml:"attrPhone" envconfig:"LDAP_ATTR_PHONE"` | 	PhoneAttribute       string `yaml:"attrPhone" envconfig:"LDAP_ATTR_PHONE"` | ||||||
| 	GroupMemberAttribute string `yaml:"attrGroups" envconfig:"LDAP_ATTR_GROUPS"` | 	GroupMemberAttribute string `yaml:"attrGroups" envconfig:"LDAP_ATTR_GROUPS"` | ||||||
| 	DisabledAttribute    string `yaml:"attrDisabled" envconfig:"LDAP_ATTR_DISABLED"` |  | ||||||
| 
 | 
 | ||||||
|  | 	LoginFilter    string `yaml:"loginFilter" envconfig:"LDAP_LOGIN_FILTER"` // {{login_identifier}} gets replaced with the login email address
 | ||||||
|  | 	SyncFilter     string `yaml:"syncFilter" envconfig:"LDAP_SYNC_FILTER"` | ||||||
| 	AdminLdapGroup string `yaml:"adminGroup" envconfig:"LDAP_ADMIN_GROUP"` // Members of this group receive admin rights in WG-Portal
 | 	AdminLdapGroup string `yaml:"adminGroup" envconfig:"LDAP_ADMIN_GROUP"` // Members of this group receive admin rights in WG-Portal
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,8 +2,6 @@ package ldap | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"crypto/tls" | 	"crypto/tls" | ||||||
| 	"fmt" |  | ||||||
| 	"strconv" |  | ||||||
| 
 | 
 | ||||||
| 	"github.com/go-ldap/ldap/v3" | 	"github.com/go-ldap/ldap/v3" | ||||||
| 	"github.com/pkg/errors" | 	"github.com/pkg/errors" | ||||||
|  | @ -54,13 +52,10 @@ func FindAllUsers(cfg *Config) ([]RawLdapData, error) { | ||||||
| 	// Search all users
 | 	// Search all users
 | ||||||
| 	attrs := []string{"dn", cfg.EmailAttribute, cfg.EmailAttribute, cfg.FirstNameAttribute, cfg.LastNameAttribute, | 	attrs := []string{"dn", cfg.EmailAttribute, cfg.EmailAttribute, cfg.FirstNameAttribute, cfg.LastNameAttribute, | ||||||
| 		cfg.PhoneAttribute, cfg.GroupMemberAttribute} | 		cfg.PhoneAttribute, cfg.GroupMemberAttribute} | ||||||
| 	if cfg.DisabledAttribute != "" { |  | ||||||
| 		attrs = append(attrs, cfg.DisabledAttribute) |  | ||||||
| 	} |  | ||||||
| 	searchRequest := ldap.NewSearchRequest( | 	searchRequest := ldap.NewSearchRequest( | ||||||
| 		cfg.BaseDN, | 		cfg.BaseDN, | ||||||
| 		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, | 		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, | ||||||
| 		fmt.Sprintf("(objectClass=%s)", cfg.UserClass), attrs, nil, | 		cfg.SyncFilter, attrs, nil, | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 	sr, err := client.Search(searchRequest) | 	sr, err := client.Search(searchRequest) | ||||||
|  | @ -87,27 +82,3 @@ func FindAllUsers(cfg *Config) ([]RawLdapData, error) { | ||||||
| 
 | 
 | ||||||
| 	return tmpData, nil | 	return tmpData, nil | ||||||
| } | } | ||||||
| 
 |  | ||||||
| func IsActiveDirectoryUserDisabled(userAccountControl string) bool { |  | ||||||
| 	if userAccountControl == "" { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	uacInt, err := strconv.ParseInt(userAccountControl, 10, 32) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 	if int32(uacInt)&0x2 != 0 { |  | ||||||
| 		return true // bit 2 set means account is disabled
 |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func IsOpenLdapUserDisabled(pwdAccountLockedTime string) bool { |  | ||||||
| 	if pwdAccountLockedTime != "" { |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -97,15 +97,14 @@ func NewConfig() *Config { | ||||||
| 	cfg.LDAP.StartTLS = true | 	cfg.LDAP.StartTLS = true | ||||||
| 	cfg.LDAP.BindUser = "company\\\\ldap_wireguard" | 	cfg.LDAP.BindUser = "company\\\\ldap_wireguard" | ||||||
| 	cfg.LDAP.BindPass = "SuperSecret" | 	cfg.LDAP.BindPass = "SuperSecret" | ||||||
| 	cfg.LDAP.Type = "AD" |  | ||||||
| 	cfg.LDAP.UserClass = "organizationalPerson" |  | ||||||
| 	cfg.LDAP.EmailAttribute = "mail" | 	cfg.LDAP.EmailAttribute = "mail" | ||||||
| 	cfg.LDAP.FirstNameAttribute = "givenName" | 	cfg.LDAP.FirstNameAttribute = "givenName" | ||||||
| 	cfg.LDAP.LastNameAttribute = "sn" | 	cfg.LDAP.LastNameAttribute = "sn" | ||||||
| 	cfg.LDAP.PhoneAttribute = "telephoneNumber" | 	cfg.LDAP.PhoneAttribute = "telephoneNumber" | ||||||
| 	cfg.LDAP.GroupMemberAttribute = "memberOf" | 	cfg.LDAP.GroupMemberAttribute = "memberOf" | ||||||
| 	cfg.LDAP.DisabledAttribute = "userAccountControl" |  | ||||||
| 	cfg.LDAP.AdminLdapGroup = "CN=WireGuardAdmins,OU=_O_IT,DC=COMPANY,DC=LOCAL" | 	cfg.LDAP.AdminLdapGroup = "CN=WireGuardAdmins,OU=_O_IT,DC=COMPANY,DC=LOCAL" | ||||||
|  | 	cfg.LDAP.LoginFilter = "(&(objectClass=organizationalPerson)(mail={{login_identifier}})(!userAccountControl:1.2.840.113556.1.4.803:=2))" | ||||||
|  | 	cfg.LDAP.SyncFilter = "(&(objectClass=organizationalPerson)(!userAccountControl:1.2.840.113556.1.4.803:=2))" | ||||||
| 
 | 
 | ||||||
| 	cfg.WG.DeviceNames = []string{"wg0"} | 	cfg.WG.DeviceNames = []string{"wg0"} | ||||||
| 	cfg.WG.DefaultDeviceName = "wg0" | 	cfg.WG.DefaultDeviceName = "wg0" | ||||||
|  |  | ||||||
|  | @ -4,6 +4,8 @@ import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/pkg/errors" | ||||||
|  | 
 | ||||||
| 	"github.com/gin-gonic/gin" | 	"github.com/gin-gonic/gin" | ||||||
| 	"github.com/h44z/wg-portal/internal/authentication" | 	"github.com/h44z/wg-portal/internal/authentication" | ||||||
| 	"github.com/h44z/wg-portal/internal/users" | 	"github.com/h44z/wg-portal/internal/users" | ||||||
|  | @ -53,65 +55,15 @@ func (s *Server) PostLogin(c *gin.Context) { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Check user database for an matching entry
 | 	// Check all available auth backends
 | ||||||
| 	var loginProvider authentication.AuthProvider | 	user, err := s.checkAuthentication(username, password) | ||||||
| 	email := "" | 	if err != nil { | ||||||
| 	user := s.users.GetUser(username) // retrieve active candidate user from db
 | 		s.GetHandleError(c, http.StatusInternalServerError, "login error", err.Error()) | ||||||
| 	if user != nil {                  // existing user
 | 		return | ||||||
| 		loginProvider = s.auth.GetProvider(string(user.Source)) |  | ||||||
| 		if loginProvider == nil { |  | ||||||
| 			s.GetHandleError(c, http.StatusInternalServerError, "login error", "login provider unavailable") |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		authEmail, err := loginProvider.Login(&authentication.AuthContext{ |  | ||||||
| 			Username: username, |  | ||||||
| 			Password: password, |  | ||||||
| 		}) |  | ||||||
| 		if err == nil { |  | ||||||
| 			email = authEmail |  | ||||||
| 		} |  | ||||||
| 	} else { // possible new user
 |  | ||||||
| 		// Check all available auth backends
 |  | ||||||
| 		for _, provider := range s.auth.GetProvidersForType(authentication.AuthProviderTypePassword) { |  | ||||||
| 			// try to log in to the given provider
 |  | ||||||
| 			authEmail, err := provider.Login(&authentication.AuthContext{ |  | ||||||
| 				Username: username, |  | ||||||
| 				Password: password, |  | ||||||
| 			}) |  | ||||||
| 			if err != nil { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			email = authEmail |  | ||||||
| 			loginProvider = provider |  | ||||||
| 
 |  | ||||||
| 			// create new user in the database (or reactivate him)
 |  | ||||||
| 			userData, err := loginProvider.GetUserModel(&authentication.AuthContext{ |  | ||||||
| 				Username: email, |  | ||||||
| 			}) |  | ||||||
| 			if err != nil { |  | ||||||
| 				s.GetHandleError(c, http.StatusInternalServerError, "login error", err.Error()) |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 			if err := s.CreateUser(users.User{ |  | ||||||
| 				Email:     userData.Email, |  | ||||||
| 				Source:    users.UserSource(loginProvider.GetName()), |  | ||||||
| 				IsAdmin:   userData.IsAdmin, |  | ||||||
| 				Firstname: userData.Firstname, |  | ||||||
| 				Lastname:  userData.Lastname, |  | ||||||
| 				Phone:     userData.Phone, |  | ||||||
| 			}, s.wg.Cfg.GetDefaultDeviceName()); err != nil { |  | ||||||
| 				s.GetHandleError(c, http.StatusInternalServerError, "login error", "failed to update user data") |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			user = s.users.GetUser(username) |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Check if user is authenticated
 | 	// Check if user is authenticated
 | ||||||
| 	if email == "" || loginProvider == nil || user == nil { | 	if user == nil { | ||||||
| 		c.Redirect(http.StatusSeeOther, "/auth/login?err=authfail") | 		c.Redirect(http.StatusSeeOther, "/auth/login?err=authfail") | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | @ -152,3 +104,48 @@ func (s *Server) GetLogout(c *gin.Context) { | ||||||
| 	} | 	} | ||||||
| 	c.Redirect(http.StatusSeeOther, "/") | 	c.Redirect(http.StatusSeeOther, "/") | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func (s *Server) checkAuthentication(username, password string) (*users.User, error) { | ||||||
|  | 	var user *users.User | ||||||
|  | 
 | ||||||
|  | 	// Check all available auth backends
 | ||||||
|  | 	for _, provider := range s.auth.GetProvidersForType(authentication.AuthProviderTypePassword) { | ||||||
|  | 		// try to log in to the given provider
 | ||||||
|  | 		authEmail, err := provider.Login(&authentication.AuthContext{ | ||||||
|  | 			Username: username, | ||||||
|  | 			Password: password, | ||||||
|  | 		}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Login succeeded
 | ||||||
|  | 		user = s.users.GetUser(authEmail) | ||||||
|  | 		if user != nil { | ||||||
|  | 			break // user exists, nothing more to do...
 | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// create new user in the database (or reactivate him)
 | ||||||
|  | 		userData, err := provider.GetUserModel(&authentication.AuthContext{ | ||||||
|  | 			Username: username, | ||||||
|  | 		}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, errors.Wrap(err, "failed to get user model") | ||||||
|  | 		} | ||||||
|  | 		if err := s.CreateUser(users.User{ | ||||||
|  | 			Email:     userData.Email, | ||||||
|  | 			Source:    users.UserSource(provider.GetName()), | ||||||
|  | 			IsAdmin:   userData.IsAdmin, | ||||||
|  | 			Firstname: userData.Firstname, | ||||||
|  | 			Lastname:  userData.Lastname, | ||||||
|  | 			Phone:     userData.Phone, | ||||||
|  | 		}, s.wg.Cfg.GetDefaultDeviceName()); err != nil { | ||||||
|  | 			return nil, errors.Wrap(err, "failed to update user data") | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		user = s.users.GetUser(authEmail) | ||||||
|  | 		break | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return user, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -32,86 +32,16 @@ func (s *Server) SyncLdapWithUserDatabase() { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		for i := range ldapUsers { | 		// Update existing LDAP users
 | ||||||
| 			// prefilter
 | 		s.updateLdapUsers(ldapUsers) | ||||||
| 			if ldapUsers[i].Attributes[s.config.LDAP.EmailAttribute] == "" || |  | ||||||
| 				ldapUsers[i].Attributes[s.config.LDAP.FirstNameAttribute] == "" || |  | ||||||
| 				ldapUsers[i].Attributes[s.config.LDAP.LastNameAttribute] == "" { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 
 | 
 | ||||||
| 			user, err := s.users.GetOrCreateUserUnscoped(ldapUsers[i].Attributes[s.config.LDAP.EmailAttribute]) | 		// Disable missing LDAP users
 | ||||||
| 			if err != nil { | 		s.disableMissingLdapUsers(ldapUsers) | ||||||
| 				logrus.Errorf("failed to get/create user %s in database: %v", ldapUsers[i].Attributes[s.config.LDAP.EmailAttribute], err) |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			// check if user should be deactivated
 |  | ||||||
| 			ldapDeactivated := false |  | ||||||
| 			switch s.config.LDAP.Type { |  | ||||||
| 			case ldap.TypeActiveDirectory: |  | ||||||
| 				ldapDeactivated = ldap.IsActiveDirectoryUserDisabled(ldapUsers[i].Attributes[s.config.LDAP.DisabledAttribute]) |  | ||||||
| 			case ldap.TypeOpenLDAP: |  | ||||||
| 				ldapDeactivated = ldap.IsOpenLdapUserDisabled(ldapUsers[i].Attributes[s.config.LDAP.DisabledAttribute]) |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			// check if user has been disabled in ldap, update peers accordingly
 |  | ||||||
| 			if ldapDeactivated != user.DeletedAt.Valid { |  | ||||||
| 				if ldapDeactivated { |  | ||||||
| 					// disable all peers for the given user
 |  | ||||||
| 					for _, peer := range s.peers.GetPeersByMail(user.Email) { |  | ||||||
| 						now := time.Now() |  | ||||||
| 						peer.DeactivatedAt = &now |  | ||||||
| 						if err = s.UpdatePeer(peer, now); err != nil { |  | ||||||
| 							logrus.Errorf("failed to update deactivated peer %s: %v", peer.PublicKey, err) |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 				} else { |  | ||||||
| 					// enable all peers for the given user
 |  | ||||||
| 					for _, peer := range s.peers.GetPeersByMail(user.Email) { |  | ||||||
| 						now := time.Now() |  | ||||||
| 						peer.DeactivatedAt = nil |  | ||||||
| 						if err = s.UpdatePeer(peer, now); err != nil { |  | ||||||
| 							logrus.Errorf("failed to update activated peer %s: %v", peer.PublicKey, err) |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			// Sync attributes from ldap
 |  | ||||||
| 			if s.UserChangedInLdap(user, &ldapUsers[i]) { |  | ||||||
| 				user.Firstname = ldapUsers[i].Attributes[s.config.LDAP.FirstNameAttribute] |  | ||||||
| 				user.Lastname = ldapUsers[i].Attributes[s.config.LDAP.LastNameAttribute] |  | ||||||
| 				user.Email = ldapUsers[i].Attributes[s.config.LDAP.EmailAttribute] |  | ||||||
| 				user.Phone = ldapUsers[i].Attributes[s.config.LDAP.PhoneAttribute] |  | ||||||
| 				user.IsAdmin = false |  | ||||||
| 				user.Source = users.UserSourceLdap |  | ||||||
| 				user.DeletedAt = gorm.DeletedAt{} // Not deleted
 |  | ||||||
| 
 |  | ||||||
| 				for _, group := range ldapUsers[i].RawAttributes[s.config.LDAP.GroupMemberAttribute] { |  | ||||||
| 					if string(group) == s.config.LDAP.AdminLdapGroup { |  | ||||||
| 						user.IsAdmin = true |  | ||||||
| 						break |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				if err = s.users.UpdateUser(user); err != nil { |  | ||||||
| 					logrus.Errorf("failed to update ldap user %s in database: %v", user.Email, err) |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				if ldapDeactivated { |  | ||||||
| 					if err = s.users.DeleteUser(user); err != nil { |  | ||||||
| 						logrus.Errorf("failed to delete deactivated user %s in database: %v", user.Email, err) |  | ||||||
| 						continue |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	logrus.Info("ldap user synchronization stopped") | 	logrus.Info("ldap user synchronization stopped") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s Server) UserChangedInLdap(user *users.User, ldapData *ldap.RawLdapData) bool { | func (s Server) userChangedInLdap(user *users.User, ldapData *ldap.RawLdapData) bool { | ||||||
| 	if user.Firstname != ldapData.Attributes[s.config.LDAP.FirstNameAttribute] { | 	if user.Firstname != ldapData.Attributes[s.config.LDAP.FirstNameAttribute] { | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
|  | @ -125,14 +55,7 @@ func (s Server) UserChangedInLdap(user *users.User, ldapData *ldap.RawLdapData) | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	ldapDeactivated := false | 	if user.DeletedAt.Valid { | ||||||
| 	switch s.config.LDAP.Type { |  | ||||||
| 	case ldap.TypeActiveDirectory: |  | ||||||
| 		ldapDeactivated = ldap.IsActiveDirectoryUserDisabled(ldapData.Attributes[s.config.LDAP.DisabledAttribute]) |  | ||||||
| 	case ldap.TypeOpenLDAP: |  | ||||||
| 		ldapDeactivated = ldap.IsOpenLdapUserDisabled(ldapData.Attributes[s.config.LDAP.DisabledAttribute]) |  | ||||||
| 	} |  | ||||||
| 	if ldapDeactivated != user.DeletedAt.Valid { |  | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -149,3 +72,82 @@ func (s Server) UserChangedInLdap(user *users.User, ldapData *ldap.RawLdapData) | ||||||
| 
 | 
 | ||||||
| 	return false | 	return false | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func (s *Server) disableMissingLdapUsers(ldapUsers []ldap.RawLdapData) { | ||||||
|  | 	// Disable missing LDAP users
 | ||||||
|  | 	activeUsers := s.users.GetUsers() | ||||||
|  | 	for i := range activeUsers { | ||||||
|  | 		if activeUsers[i].Source != users.UserSourceLdap { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		existsInLDAP := false | ||||||
|  | 		for j := range ldapUsers { | ||||||
|  | 			if activeUsers[i].Email == ldapUsers[j].Attributes[s.config.LDAP.EmailAttribute] { | ||||||
|  | 				existsInLDAP = true | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if existsInLDAP { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// disable all peers for the given user
 | ||||||
|  | 		for _, peer := range s.peers.GetPeersByMail(activeUsers[i].Email) { | ||||||
|  | 			now := time.Now() | ||||||
|  | 			peer.DeactivatedAt = &now | ||||||
|  | 			if err := s.UpdatePeer(peer, now); err != nil { | ||||||
|  | 				logrus.Errorf("failed to update deactivated peer %s: %v", peer.PublicKey, err) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if err := s.users.DeleteUser(&activeUsers[i]); err != nil { | ||||||
|  | 			logrus.Errorf("failed to delete deactivated user %s in database: %v", activeUsers[i].Email, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Server) updateLdapUsers(ldapUsers []ldap.RawLdapData) { | ||||||
|  | 	for i := range ldapUsers { | ||||||
|  | 		user, err := s.users.GetOrCreateUserUnscoped(ldapUsers[i].Attributes[s.config.LDAP.EmailAttribute]) | ||||||
|  | 		if err != nil { | ||||||
|  | 			logrus.Errorf("failed to get/create user %s in database: %v", ldapUsers[i].Attributes[s.config.LDAP.EmailAttribute], err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// re-enable LDAP user if the user was disabled
 | ||||||
|  | 		if user.DeletedAt.Valid { | ||||||
|  | 			// enable all peers for the given user
 | ||||||
|  | 			for _, peer := range s.peers.GetPeersByMail(user.Email) { | ||||||
|  | 				now := time.Now() | ||||||
|  | 				peer.DeactivatedAt = nil | ||||||
|  | 				if err = s.UpdatePeer(peer, now); err != nil { | ||||||
|  | 					logrus.Errorf("failed to update activated peer %s: %v", peer.PublicKey, err) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Sync attributes from ldap
 | ||||||
|  | 		if s.userChangedInLdap(user, &ldapUsers[i]) { | ||||||
|  | 			user.Firstname = ldapUsers[i].Attributes[s.config.LDAP.FirstNameAttribute] | ||||||
|  | 			user.Lastname = ldapUsers[i].Attributes[s.config.LDAP.LastNameAttribute] | ||||||
|  | 			user.Email = ldapUsers[i].Attributes[s.config.LDAP.EmailAttribute] | ||||||
|  | 			user.Phone = ldapUsers[i].Attributes[s.config.LDAP.PhoneAttribute] | ||||||
|  | 			user.IsAdmin = false | ||||||
|  | 			user.Source = users.UserSourceLdap | ||||||
|  | 			user.DeletedAt = gorm.DeletedAt{} // Not deleted
 | ||||||
|  | 
 | ||||||
|  | 			for _, group := range ldapUsers[i].RawAttributes[s.config.LDAP.GroupMemberAttribute] { | ||||||
|  | 				if string(group) == s.config.LDAP.AdminLdapGroup { | ||||||
|  | 					user.IsAdmin = true | ||||||
|  | 					break | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if err = s.users.UpdateUser(user); err != nil { | ||||||
|  | 				logrus.Errorf("failed to update ldap user %s in database: %v", user.Email, err) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -6,7 +6,6 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/gin-gonic/gin" | 	"github.com/gin-gonic/gin" | ||||||
| 	wgportal "github.com/h44z/wg-portal" | 	wgportal "github.com/h44z/wg-portal" | ||||||
| 	"github.com/h44z/wg-portal/internal/authentication" |  | ||||||
| 	_ "github.com/h44z/wg-portal/internal/server/docs" // docs is generated by Swag CLI, you have to import it.
 | 	_ "github.com/h44z/wg-portal/internal/server/docs" // docs is generated by Swag CLI, you have to import it.
 | ||||||
| 	ginSwagger "github.com/swaggo/gin-swagger" | 	ginSwagger "github.com/swaggo/gin-swagger" | ||||||
| 	"github.com/swaggo/gin-swagger/swaggerFiles" | 	"github.com/swaggo/gin-swagger/swaggerFiles" | ||||||
|  | @ -162,28 +161,16 @@ func (s *Server) RequireApiAuthentication(scope string) gin.HandlerFunc { | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// Check user database for an matching entry
 | 		// Check all available auth backends
 | ||||||
| 		var loginProvider authentication.AuthProvider | 		user, err := s.checkAuthentication(username, password) | ||||||
| 		user := s.users.GetUser(username) // retrieve active candidate user from db
 | 		if err != nil { | ||||||
| 		if user == nil || user.Email == "" { |  | ||||||
| 			c.Abort() | 			c.Abort() | ||||||
| 			c.JSON(http.StatusUnauthorized, ApiError{Message: "unauthorized"}) | 			c.JSON(http.StatusInternalServerError, ApiError{Message: "login error"}) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		loginProvider = s.auth.GetProvider(string(user.Source)) | 		// Check if user is authenticated
 | ||||||
| 		if loginProvider == nil { | 		if user == nil { | ||||||
| 			c.Abort() |  | ||||||
| 			c.JSON(http.StatusUnauthorized, ApiError{Message: "unauthorized"}) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		authEmail, err := loginProvider.Login(&authentication.AuthContext{ |  | ||||||
| 			Username: username, |  | ||||||
| 			Password: password, |  | ||||||
| 		}) |  | ||||||
| 
 |  | ||||||
| 		// Test if authentication succeeded
 |  | ||||||
| 		if err != nil || authEmail == "" { |  | ||||||
| 			c.Abort() | 			c.Abort() | ||||||
| 			c.JSON(http.StatusUnauthorized, ApiError{Message: "unauthorized"}) | 			c.JSON(http.StatusUnauthorized, ApiError{Message: "unauthorized"}) | ||||||
| 			return | 			return | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue