// Copyright 2019 Yunion // // 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 db import ( "context" "fmt" "regexp" "strings" "yunion.io/x/jsonutils" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/seclib" "yunion.io/x/sqlchemy" "yunion.io/x/onecloud/pkg/cloudcommon/consts" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/stringutils2" ) func isNameUnique(ctx context.Context, manager IModelManager, ownerId mcclient.IIdentityProvider, name string, uniqValues jsonutils.JSONObject) (bool, error) { return isRawNameUnique(ctx, manager, ownerId, name, uniqValues, false) } func isRawNameUnique(ctx context.Context, manager IModelManager, ownerId mcclient.IIdentityProvider, name string, uniqValues jsonutils.JSONObject, isRaw bool) (bool, error) { var q *sqlchemy.SQuery if isRaw { q = manager.TableSpec().Instance().Query() } else { q = manager.Query() } q = manager.FilterByName(q, name) q = manager.FilterByOwner(ctx, q, manager, nil, ownerId, manager.NamespaceScope()) if !isRaw { q = manager.FilterBySystemAttributes(q, nil, nil, manager.ResourceScope()) if uniqValues != nil { q = manager.FilterByUniqValues(q, uniqValues) } } cnt, err := q.CountWithError() if err != nil { return false, err } return cnt == 0, nil } const forbiddenNameChars = "/\\;\n\r\t" func NewNameValidator(ctx context.Context, manager IModelManager, ownerId mcclient.IIdentityProvider, name string, uniqValues jsonutils.JSONObject) error { err := manager.ValidateName(name) if err != nil { return err } if strings.ContainsAny(name, forbiddenNameChars) { return errors.Wrapf(errors.ErrInvalidFormat, "name should not contains any of %q", forbiddenNameChars) } uniq, err := isNameUnique(ctx, manager, ownerId, name, uniqValues) if err != nil { return err } if !uniq { return httperrors.NewDuplicateNameError(manager.Keyword(), name) } return nil } func isAlterNameUnique(ctx context.Context, model IModel, name string) (bool, error) { return isRawAlterNameUnique(ctx, model, name, false) } func isRawAlterNameUnique(ctx context.Context, model IModel, name string, isRaw bool) (bool, error) { manager := model.GetModelManager() var q *sqlchemy.SQuery if isRaw { q = manager.TableSpec().Instance().Query() } else { q = manager.Query() } q = manager.FilterByName(q, name) q = manager.FilterByOwner(ctx, q, manager, nil, model.GetOwnerId(), manager.NamespaceScope()) q = manager.FilterByNotId(q, model.GetId()) if !isRaw { q = manager.FilterBySystemAttributes(q, nil, nil, manager.ResourceScope()) if uniqValues := model.GetUniqValues(); uniqValues != nil { q = manager.FilterByUniqValues(q, uniqValues) } } cnt, err := q.CountWithError() if err != nil { return false, err } return cnt == 0, nil } func alterNameValidator(ctx context.Context, model IModel, name string) error { err := model.GetModelManager().ValidateName(name) if err != nil { return err } if strings.ContainsAny(name, forbiddenNameChars) { return errors.Wrapf(errors.ErrInvalidFormat, "name should not contains any of %q", forbiddenNameChars) } uniq, err := isAlterNameUnique(ctx, model, name) if err != nil { return err } if !uniq { return httperrors.NewDuplicateNameError("name", name) } return nil } func GenerateName(ctx context.Context, manager IModelManager, ownerId mcclient.IIdentityProvider, hint string) (string, error) { return GenerateName2(ctx, manager, ownerId, hint, nil, 1) } func GenerateAlterName(model IModel, hint string) (string, error) { if hint == model.GetName() { return hint, nil } return GenerateName2(nil, nil, nil, hint, model, 1) } func GenerateName2(ctx context.Context, manager IModelManager, ownerId mcclient.IIdentityProvider, hint string, model IModel, baseIndex int) (string, error) { _, pattern, patternLen, offset, ch := stringutils2.ParseNamePattern2(hint) var name string if patternLen == 0 { name = hint } else { if offset > 0 { baseIndex = offset } switch ch { case stringutils2.RandomChar: name = fmt.Sprintf(pattern, strings.ToLower(seclib.RandomPassword(patternLen))) case stringutils2.RepChar: fallthrough default: name = fmt.Sprintf(pattern, baseIndex) baseIndex += 1 } } for { var uniq bool var err error if model == nil { uniq, err = isRawNameUnique(ctx, manager, ownerId, name, nil, consts.IsHistoricalUniqueName()) } else { uniq, err = isRawAlterNameUnique(ctx, model, name, consts.IsHistoricalUniqueName()) } if err != nil { return "", err } if uniq { return name, nil } switch ch { case stringutils2.RandomChar: name = fmt.Sprintf(pattern, strings.ToLower(seclib.RandomPassword(patternLen))) case stringutils2.RepChar: fallthrough default: name = fmt.Sprintf(pattern, baseIndex) baseIndex += 1 } } } var ( dnsNameREG = regexp.MustCompile(`^[a-z][a-z0-9-]*$`) ) type SDnsNameValidatorManager struct{} func (manager *SDnsNameValidatorManager) ValidateName(name string) error { if dnsNameREG.MatchString(name) { return nil } return httperrors.NewInputParameterError("name starts with letter, and contains letter, number and - only") } var ( hostNameREG = regexp.MustCompile(`^[a-z$][a-z0-9-${}.]*$`) ) type SHostNameValidatorManager struct{} func (manager *SHostNameValidatorManager) ValidateName(name string) error { if hostNameREG.MatchString(name) { return nil } return httperrors.NewInputParameterError("name starts with letter, and contains letter, number and - only") }