mirror of
https://github.com/matrix-org/dendrite.git
synced 2025-12-07 06:53:09 -06:00
Delete internal/validate.go
This commit is contained in:
parent
35611c476f
commit
aa97911b89
|
|
@ -1,291 +0,0 @@
|
||||||
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/userutil"
|
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
|
||||||
"github.com/matrix-org/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
minPasswordLength = 8 // Minimum password length
|
|
||||||
maxPasswordLength = 512 // Maximum password length
|
|
||||||
maxUsernameLength = 254 // Maximum username length
|
|
||||||
sessionIDLength = 24 // Session ID length
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrPasswordTooLong = errors.New("password is too long")
|
|
||||||
ErrPasswordWeak = errors.New("password does not meet the strength requirements")
|
|
||||||
ErrUsernameTooLong = fmt.Errorf("username exceeds the maximum length of %d characters", maxUsernameLength)
|
|
||||||
ErrUsernameInvalid = errors.New("username can only contain characters a-z, 0-9, or '_+-./='")
|
|
||||||
ErrUsernameUnderscore = errors.New("username cannot start with a '_'")
|
|
||||||
validUsernameRegex = regexp.MustCompile(`^[0-9a-z_\-+=./]+$`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// PasswordConfig defines the configurable parameters for password validation
|
|
||||||
type PasswordConfig struct {
|
|
||||||
MinLength int `yaml:"min_length"`
|
|
||||||
MaxLength int `yaml:"max_length"`
|
|
||||||
RequireUppercase bool `yaml:"require_uppercase"`
|
|
||||||
RequireLowercase bool `yaml:"require_lowercase"`
|
|
||||||
RequireDigit bool `yaml:"require_digit"`
|
|
||||||
RequireSpecial bool `yaml:"require_special"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default password config
|
|
||||||
var defaultPasswordConfig = PasswordConfig{
|
|
||||||
MinLength: minPasswordLength,
|
|
||||||
MaxLength: maxPasswordLength,
|
|
||||||
RequireUppercase: true,
|
|
||||||
RequireLowercase: true,
|
|
||||||
RequireDigit: true,
|
|
||||||
RequireSpecial: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidatePassword returns an error if the password is invalid according to the config
|
|
||||||
func ValidatePassword(password string, config PasswordConfig) error {
|
|
||||||
if len(password) > config.MaxLength {
|
|
||||||
return ErrPasswordTooLong
|
|
||||||
} else if len(password) < config.MinLength {
|
|
||||||
return ErrPasswordWeak
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasUppercase, hasLowercase, hasDigit, hasSpecial bool
|
|
||||||
for _, char := range password {
|
|
||||||
switch {
|
|
||||||
case unicode.IsUpper(char):
|
|
||||||
hasUppercase = true
|
|
||||||
case unicode.IsLower(char):
|
|
||||||
hasLowercase = true
|
|
||||||
case unicode.IsDigit(char):
|
|
||||||
hasDigit = true
|
|
||||||
case unicode.IsPunct(char) || unicode.IsSymbol(char):
|
|
||||||
hasSpecial = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.RequireUppercase && !hasUppercase {
|
|
||||||
return ErrPasswordWeak
|
|
||||||
}
|
|
||||||
if config.RequireLowercase && !hasLowercase {
|
|
||||||
return ErrPasswordWeak
|
|
||||||
}
|
|
||||||
if config.RequireDigit && !hasDigit {
|
|
||||||
return ErrPasswordWeak
|
|
||||||
}
|
|
||||||
if config.RequireSpecial && !hasSpecial {
|
|
||||||
return ErrPasswordWeak
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sidecar: Log the password validation attempt (placeholder)
|
|
||||||
sidecarLogPasswordValidation(password)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sidecar function to log password validation attempts
|
|
||||||
func sidecarLogPasswordValidation(password string) {
|
|
||||||
// Placeholder for sidecar logging
|
|
||||||
fmt.Println("Sidecar log: Password validation attempted")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Example usage
|
|
||||||
password := "P@ssw0rd"
|
|
||||||
err := ValidatePassword(password, defaultPasswordConfig)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Password validation failed:", err)
|
|
||||||
} else {
|
|
||||||
fmt.Println("Password is valid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateUsername returns an error if the username is invalid
|
|
||||||
func ValidateUsername(localpart string, domain spec.ServerName) error {
|
|
||||||
// https://github.com/matrix-org/synapse/blob/v0.20.0/synapse/rest/client/v2_alpha/register.py#L161
|
|
||||||
if id := fmt.Sprintf("@%s:%s", localpart, domain); len(id) > maxUsernameLength {
|
|
||||||
return ErrUsernameTooLong
|
|
||||||
} else if !validUsernameRegex.MatchString(localpart) {
|
|
||||||
return ErrUsernameInvalid
|
|
||||||
} else if localpart[0] == '_' { // Regex checks its not a zero length string
|
|
||||||
return ErrUsernameUnderscore
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UsernameResponse returns a util.JSONResponse for the given error, if any.
|
|
||||||
func UsernameResponse(err error) *util.JSONResponse {
|
|
||||||
switch err {
|
|
||||||
case ErrUsernameTooLong:
|
|
||||||
return &util.JSONResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
JSON: spec.BadJSON(err.Error()),
|
|
||||||
}
|
|
||||||
case ErrUsernameInvalid, ErrUsernameUnderscore:
|
|
||||||
return &util.JSONResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
JSON: spec.InvalidUsername(err.Error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateApplicationServiceUsername returns an error if the username is invalid for an application service
|
|
||||||
func ValidateApplicationServiceUsername(localpart string, domain spec.ServerName) error {
|
|
||||||
userID := userutil.MakeUserID(localpart, domain)
|
|
||||||
return ValidateApplicationServiceUserID(userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ValidateApplicationServiceUserID(userID string) error {
|
|
||||||
if len(userID) > maxUsernameLength {
|
|
||||||
return ErrUsernameTooLong
|
|
||||||
}
|
|
||||||
|
|
||||||
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
|
||||||
if err != nil || !validUsernameRegex.MatchString(localpart) {
|
|
||||||
return ErrUsernameInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// userIDIsWithinApplicationServiceNamespace checks to see if a given userID
|
|
||||||
// falls within any of the namespaces of a given Application Service. If no
|
|
||||||
// Application Service is given, it will check to see if it matches any
|
|
||||||
// Application Service's namespace.
|
|
||||||
func userIDIsWithinApplicationServiceNamespace(
|
|
||||||
cfg *config.ClientAPI,
|
|
||||||
userID string,
|
|
||||||
appservice *config.ApplicationService,
|
|
||||||
) bool {
|
|
||||||
var localpart, domain, err = gomatrixserverlib.SplitID('@', userID)
|
|
||||||
if err != nil {
|
|
||||||
// Not a valid userID
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cfg.Matrix.IsLocalServerName(domain) {
|
|
||||||
// This is a federated userID
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if localpart == appservice.SenderLocalpart {
|
|
||||||
// This is the application service bot userID
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop through given application service's namespaces and see if any match
|
|
||||||
for _, namespace := range appservice.NamespaceMap["users"] {
|
|
||||||
// Application service namespaces are checked for validity in config
|
|
||||||
if namespace.RegexpObject.MatchString(userID) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// usernameMatchesMultipleExclusiveNamespaces will check if a given username matches
|
|
||||||
// more than one exclusive namespace. More than one is not allowed
|
|
||||||
func userIDMatchesMultipleExclusiveNamespaces(
|
|
||||||
cfg *config.ClientAPI,
|
|
||||||
userID string,
|
|
||||||
) bool {
|
|
||||||
// Check namespaces and see if more than one match
|
|
||||||
matchCount := 0
|
|
||||||
for _, appservice := range cfg.Derived.ApplicationServices {
|
|
||||||
if appservice.OwnsNamespaceCoveringUserId(userID) {
|
|
||||||
if matchCount++; matchCount > 1 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateApplicationServiceRequest checks if a provided application service
|
|
||||||
// token corresponds to one that is registered, and, if so, checks if the
|
|
||||||
// supplied userIDOrLocalpart is within that application service's namespace.
|
|
||||||
//
|
|
||||||
// As long as these two requirements are met, the matched application service
|
|
||||||
// ID will be returned. Otherwise, it will return a JSON response with the
|
|
||||||
// appropriate error message.
|
|
||||||
func ValidateApplicationServiceRequest(
|
|
||||||
cfg *config.ClientAPI,
|
|
||||||
userIDOrLocalpart string,
|
|
||||||
accessToken string,
|
|
||||||
) (string, *util.JSONResponse) {
|
|
||||||
localpart, domain, err := userutil.ParseUsernameParam(userIDOrLocalpart, cfg.Matrix)
|
|
||||||
if err != nil {
|
|
||||||
return "", &util.JSONResponse{
|
|
||||||
Code: http.StatusUnauthorized,
|
|
||||||
JSON: spec.InvalidUsername(err.Error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
userID := userutil.MakeUserID(localpart, domain)
|
|
||||||
|
|
||||||
// Check if the token if the application service is valid with one we have
|
|
||||||
// registered in the config.
|
|
||||||
var matchedApplicationService *config.ApplicationService
|
|
||||||
for _, appservice := range cfg.Derived.ApplicationServices {
|
|
||||||
if appservice.ASToken == accessToken {
|
|
||||||
matchedApplicationService = &appservice
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if matchedApplicationService == nil {
|
|
||||||
return "", &util.JSONResponse{
|
|
||||||
Code: http.StatusUnauthorized,
|
|
||||||
JSON: spec.UnknownToken("Supplied access_token does not match any known application service"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the desired username is within at least one of the application service's namespaces.
|
|
||||||
if !userIDIsWithinApplicationServiceNamespace(cfg, userID, matchedApplicationService) {
|
|
||||||
// If we didn't find any matches, return M_EXCLUSIVE
|
|
||||||
return "", &util.JSONResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
JSON: spec.ASExclusive(fmt.Sprintf(
|
|
||||||
"Supplied username %s did not match any namespaces for application service ID: %s", userIDOrLocalpart, matchedApplicationService.ID)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check this user does not fit multiple application service namespaces
|
|
||||||
if userIDMatchesMultipleExclusiveNamespaces(cfg, userID) {
|
|
||||||
return "", &util.JSONResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
JSON: spec.ASExclusive(fmt.Sprintf(
|
|
||||||
"Supplied username %s matches multiple exclusive application service namespaces. Only 1 match allowed", userIDOrLocalpart)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check username application service is trying to register is valid
|
|
||||||
if err := ValidateApplicationServiceUserID(userID); err != nil {
|
|
||||||
return "", UsernameResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// No errors, registration valid
|
|
||||||
return matchedApplicationService.ID, nil
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue