Files
opencloud/vendor/github.com/libregraph/idm/pkg/ldapserver/pwmodifyexop.go
2023-04-19 20:24:34 +02:00

120 lines
3.4 KiB
Go

package ldapserver
import (
"errors"
"fmt"
"net"
ber "github.com/go-asn1-ber/asn1-ber"
"github.com/go-ldap/ldap/v3"
"github.com/libregraph/idm/pkg/ldappassword"
)
const pwmodOID = "1.3.6.1.4.1.4203.1.11.1"
const (
TagReqIdentity = 0
TagReqOldPW = 1
TagReqNewPW = 2
TagRespGenPW = 0
)
func init() {
RegisterExtendedOperation(pwmodOID, HandlePasswordModifyExOp)
}
func HandlePasswordModifyExOp(req *ber.Packet, boundDN string, server *Server, conn net.Conn) (*ber.Packet, error) {
var passwordGenerated bool
logger.V(1).Info("HandlePasswordModifyExOp")
if boundDN == "" {
return nil, ldap.NewError(ldap.LDAPResultUnwillingToPerform, errors.New("authentication required"))
}
pwReq, err := parsePasswordModifyExop(req)
if err != nil {
return nil, err
}
// If `UserIdentity` is empty, this is a request to update the bound user's own password
if pwReq.UserIdentity == "" {
pwReq.UserIdentity = boundDN
}
if pwReq.NewPassword == "" {
// New password empty means, we're requested to generate a new password
var err error
if pwReq.NewPassword, err = ldappassword.GenerateRandomPassword(server.GeneratedPasswordLength); err != nil {
logger.Error(err, "Failed to generate new password")
return nil, ldap.NewError(ldap.LDAPResultOperationsError, errors.New("Failed to generate new Password"))
}
passwordGenerated = true
}
pwReq.NewPassword, err = ldappassword.Hash(pwReq.NewPassword, "{ARGON2}")
if err != nil {
return nil, ldap.NewError(ldap.LDAPResultOperationsError, err)
}
logger.V(1).Info("Modify password extended operation", "dn", pwReq.UserIdentity)
fnNames := []string{}
for k := range server.PasswordExOpFns {
fnNames = append(fnNames, k)
}
fn := routeFunc(pwReq.UserIdentity, fnNames)
var pwUpdatefn PasswordUpdater
if pwUpdatefn = server.PasswordExOpFns[fn]; pwUpdatefn == nil {
if fn == "" {
err = fmt.Errorf("no suitable handler found for dn: '%s'", pwReq.UserIdentity)
} else {
err = fmt.Errorf("handler '%s' does not support add", fn)
}
return nil, ldap.NewError(ldap.LDAPResultUnwillingToPerform, err)
}
code, err := pwUpdatefn.ModifyPasswordExop(boundDN, pwReq, conn)
if code != ldap.LDAPResultSuccess {
return nil, ldap.NewError(uint16(code), err)
}
var response *ber.Packet
if passwordGenerated {
response = ber.NewSequence("PasswdModifyResponseValue")
response.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, pwReq.NewPassword, "genPasswd"))
}
return response, nil
}
func parsePasswordModifyExop(req *ber.Packet) (*ldap.PasswordModifyRequest, error) {
pwReq := ldap.PasswordModifyRequest{}
// An absent (or empty) body of the request is valid. Translates into: "generate a new password for
// for the current user"
if req == nil {
return &pwReq, nil
}
inner := ber.DecodePacket(req.Data.Bytes())
if inner == nil {
return &pwReq, nil
}
if len(inner.Children) > 3 {
return nil, ldap.NewError(ldap.LDAPResultDecodingError, errors.New("invalid request"))
}
for _, kid := range inner.Children {
if kid.ClassType != ber.ClassContext {
return nil, ldap.NewError(ldap.LDAPResultDecodingError, errors.New("invalid request"))
}
switch kid.Tag {
default:
return nil, ldap.NewError(ldap.LDAPResultDecodingError, errors.New("invalid request"))
case TagReqIdentity:
pwReq.UserIdentity = kid.Data.String()
case TagReqOldPW:
pwReq.OldPassword = kid.Data.String()
case TagReqNewPW:
pwReq.NewPassword = kid.Data.String()
}
}
return &pwReq, nil
}