Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit01a904c

Browse files
feat(codersdk): export name validators (#14550)
* feat(codersdk): export name validators* review
1 parent093d243 commit01a904c

File tree

10 files changed

+154
-31
lines changed

10 files changed

+154
-31
lines changed

‎cli/usercreate.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"github.com/coder/pretty"
1212

1313
"github.com/coder/coder/v2/cli/cliui"
14-
"github.com/coder/coder/v2/coderd/httpapi"
1514
"github.com/coder/coder/v2/codersdk"
1615
"github.com/coder/coder/v2/cryptorand"
1716
"github.com/coder/serpent"
@@ -72,7 +71,7 @@ func (r *RootCmd) userCreate() *serpent.Command {
7271
iferr!=nil {
7372
returnerr
7473
}
75-
name=httpapi.NormalizeRealUsername(rawName)
74+
name=codersdk.NormalizeRealUsername(rawName)
7675
if!strings.EqualFold(rawName,name) {
7776
cliui.Warnf(inv.Stderr,"Normalized name to %q",name)
7877
}

‎coderd/externalauth/externalauth.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323

2424
"github.com/coder/coder/v2/coderd/database"
2525
"github.com/coder/coder/v2/coderd/database/dbtime"
26-
"github.com/coder/coder/v2/coderd/httpapi"
2726
"github.com/coder/coder/v2/coderd/promoauth"
2827
"github.com/coder/coder/v2/codersdk"
2928
"github.com/coder/retry"
@@ -486,7 +485,7 @@ func ConvertConfig(instrument *promoauth.Factory, entries []codersdk.ExternalAut
486485
// apply their client secret and ID, and have the UI appear nicely.
487486
applyDefaultsToConfig(&entry)
488487

489-
valid:=httpapi.NameValid(entry.ID)
488+
valid:=codersdk.NameValid(entry.ID)
490489
ifvalid!=nil {
491490
returnnil,xerrors.Errorf("external auth provider %q doesn't have a valid id: %w",entry.ID,valid)
492491
}

‎coderd/httpapi/httpapi.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func init() {
4343
if!ok {
4444
returnfalse
4545
}
46-
valid:=NameValid(str)
46+
valid:=codersdk.NameValid(str)
4747
returnvalid==nil
4848
}
4949
for_,tag:=range []string{"username","organization_name","template_name","workspace_name","oauth2_app_name"} {
@@ -59,7 +59,7 @@ func init() {
5959
if!ok {
6060
returnfalse
6161
}
62-
valid:=DisplayNameValid(str)
62+
valid:=codersdk.DisplayNameValid(str)
6363
returnvalid==nil
6464
}
6565
for_,displayNameTag:=range []string{"organization_display_name","template_display_name","group_display_name"} {
@@ -75,7 +75,7 @@ func init() {
7575
if!ok {
7676
returnfalse
7777
}
78-
valid:=TemplateVersionNameValid(str)
78+
valid:=codersdk.TemplateVersionNameValid(str)
7979
returnvalid==nil
8080
}
8181
err:=Validate.RegisterValidation("template_version_name",templateVersionNameValidator)
@@ -89,7 +89,7 @@ func init() {
8989
if!ok {
9090
returnfalse
9191
}
92-
valid:=UserRealNameValid(str)
92+
valid:=codersdk.UserRealNameValid(str)
9393
returnvalid==nil
9494
}
9595
err=Validate.RegisterValidation("user_real_name",userRealNameValidator)
@@ -103,7 +103,7 @@ func init() {
103103
if!ok {
104104
returnfalse
105105
}
106-
valid:=GroupNameValid(str)
106+
valid:=codersdk.GroupNameValid(str)
107107
returnvalid==nil
108108
}
109109
err=Validate.RegisterValidation("group_name",groupNameValidator)

‎coderd/httpapi/name.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func UsernameFrom(str string) string {
3838
}
3939

4040
// NameValid returns whether the input string is a valid name.
41-
// It is a generic validator for any name(user, workspace, template, role name, etc.).
41+
// It is a generic validator for any namethat doesn't have it's own validator.
4242
funcNameValid(strstring)error {
4343
iflen(str)>32 {
4444
returnxerrors.New("must be <= 32 characters")

‎coderd/userauth.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,7 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
602602
}
603603

604604
ghName:=ghUser.GetName()
605-
normName:=httpapi.NormalizeRealUsername(ghName)
605+
normName:=codersdk.NormalizeRealUsername(ghName)
606606

607607
// If we have a nil GitHub ID, that is a big problem. That would mean we link
608608
// this user and all other users with this bug to the same uuid.
@@ -951,15 +951,15 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
951951
// The username is a required property in Coder. We make a best-effort
952952
// attempt at using what the claims provide, but if that fails we will
953953
// generate a random username.
954-
usernameValid:=httpapi.NameValid(username)
954+
usernameValid:=codersdk.NameValid(username)
955955
ifusernameValid!=nil {
956956
// If no username is provided, we can default to use the email address.
957957
// This will be converted in the from function below, so it's safe
958958
// to keep the domain.
959959
ifusername=="" {
960960
username=email
961961
}
962-
username=httpapi.UsernameFrom(username)
962+
username=codersdk.UsernameFrom(username)
963963
}
964964

965965
iflen(api.OIDCConfig.EmailDomain)>0 {
@@ -994,7 +994,7 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
994994
nameRaw,ok:=mergedClaims[api.OIDCConfig.NameField]
995995
ifok {
996996
name,_=nameRaw.(string)
997-
name=httpapi.NormalizeRealUsername(name)
997+
name=codersdk.NormalizeRealUsername(name)
998998
}
999999

10001000
varpicturestring
@@ -1389,7 +1389,7 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
13891389
fori:=0;i<10;i++ {
13901390
alternate:=fmt.Sprintf("%s-%s",original,namesgenerator.GetRandomName(1))
13911391

1392-
params.Username=httpapi.UsernameFrom(alternate)
1392+
params.Username=codersdk.UsernameFrom(alternate)
13931393

13941394
//nolint:gocritic
13951395
_,err:=tx.GetUserByEmailOrUsername(dbauthz.AsSystemRestricted(ctx), database.GetUserByEmailOrUsernameParams{

‎coderd/users.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,7 +1287,7 @@ type CreateUserRequest struct {
12871287
func (api*API)CreateUser(ctx context.Context,store database.Store,reqCreateUserRequest) (database.User,error) {
12881288
// Ensure the username is valid. It's the caller's responsibility to ensure
12891289
// the username is valid and unique.
1290-
ifusernameValid:=httpapi.NameValid(req.Username);usernameValid!=nil {
1290+
ifusernameValid:=codersdk.NameValid(req.Username);usernameValid!=nil {
12911291
return database.User{},xerrors.Errorf("invalid username %q: %w",req.Username,usernameValid)
12921292
}
12931293

@@ -1299,7 +1299,7 @@ func (api *API) CreateUser(ctx context.Context, store database.Store, req Create
12991299
ID:uuid.New(),
13001300
Email:req.Email,
13011301
Username:req.Username,
1302-
Name:httpapi.NormalizeRealUsername(req.Name),
1302+
Name:codersdk.NormalizeRealUsername(req.Name),
13031303
CreatedAt:dbtime.Now(),
13041304
UpdatedAt:dbtime.Now(),
13051305
HashedPassword: []byte{},

‎codersdk/name.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package codersdk
2+
3+
import (
4+
"regexp"
5+
"strings"
6+
7+
"github.com/moby/moby/pkg/namesgenerator"
8+
"golang.org/x/xerrors"
9+
)
10+
11+
var (
12+
UsernameValidRegex=regexp.MustCompile("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*$")
13+
usernameReplace=regexp.MustCompile("[^a-zA-Z0-9-]*")
14+
15+
templateVersionName=regexp.MustCompile(`^[a-zA-Z0-9]+(?:[_.-]{1}[a-zA-Z0-9]+)*$`)
16+
templateDisplayName=regexp.MustCompile(`^[^\s](.*[^\s])?$`)
17+
)
18+
19+
// UsernameFrom returns a best-effort username from the provided string.
20+
//
21+
// It first attempts to validate the incoming string, which will
22+
// be returned if it is valid. It then will attempt to extract
23+
// the username from an email address. If no success happens during
24+
// these steps, a random username will be returned.
25+
funcUsernameFrom(strstring)string {
26+
ifvalid:=NameValid(str);valid==nil {
27+
returnstr
28+
}
29+
emailAt:=strings.LastIndex(str,"@")
30+
ifemailAt>=0 {
31+
str=str[:emailAt]
32+
}
33+
str=usernameReplace.ReplaceAllString(str,"")
34+
ifvalid:=NameValid(str);valid==nil {
35+
returnstr
36+
}
37+
returnstrings.ReplaceAll(namesgenerator.GetRandomName(1),"_","-")
38+
}
39+
40+
// NameValid returns whether the input string is a valid name.
41+
// It is a generic validator for any name (user, workspace, template, role name, etc.).
42+
funcNameValid(strstring)error {
43+
iflen(str)>32 {
44+
returnxerrors.New("must be <= 32 characters")
45+
}
46+
iflen(str)<1 {
47+
returnxerrors.New("must be >= 1 character")
48+
}
49+
// Avoid conflicts with routes like /templates/new and /groups/create.
50+
ifstr=="new"||str=="create" {
51+
returnxerrors.Errorf("cannot use %q as a name",str)
52+
}
53+
matched:=UsernameValidRegex.MatchString(str)
54+
if!matched {
55+
returnxerrors.New("must be alphanumeric with hyphens")
56+
}
57+
returnnil
58+
}
59+
60+
// TemplateVersionNameValid returns whether the input string is a valid template version name.
61+
funcTemplateVersionNameValid(strstring)error {
62+
iflen(str)>64 {
63+
returnxerrors.New("must be <= 64 characters")
64+
}
65+
matched:=templateVersionName.MatchString(str)
66+
if!matched {
67+
returnxerrors.New("must be alphanumeric with underscores and dots")
68+
}
69+
returnnil
70+
}
71+
72+
// DisplayNameValid returns whether the input string is a valid template display name.
73+
funcDisplayNameValid(strstring)error {
74+
iflen(str)==0 {
75+
returnnil// empty display_name is correct
76+
}
77+
iflen(str)>64 {
78+
returnxerrors.New("must be <= 64 characters")
79+
}
80+
matched:=templateDisplayName.MatchString(str)
81+
if!matched {
82+
returnxerrors.New("must be alphanumeric with spaces")
83+
}
84+
returnnil
85+
}
86+
87+
// UserRealNameValid returns whether the input string is a valid real user name.
88+
funcUserRealNameValid(strstring)error {
89+
iflen(str)>128 {
90+
returnxerrors.New("must be <= 128 characters")
91+
}
92+
93+
ifstrings.TrimSpace(str)!=str {
94+
returnxerrors.New("must not have leading or trailing whitespace")
95+
}
96+
returnnil
97+
}
98+
99+
// GroupNameValid returns whether the input string is a valid group name.
100+
funcGroupNameValid(strstring)error {
101+
// 36 is to support using UUIDs as the group name.
102+
iflen(str)>36 {
103+
returnxerrors.New("must be <= 36 characters")
104+
}
105+
// Avoid conflicts with routes like /groups/new and /groups/create.
106+
ifstr=="new"||str=="create" {
107+
returnxerrors.Errorf("cannot use %q as a name",str)
108+
}
109+
matched:=UsernameValidRegex.MatchString(str)
110+
if!matched {
111+
returnxerrors.New("must be alphanumeric with hyphens")
112+
}
113+
returnnil
114+
}
115+
116+
// NormalizeUserRealName normalizes a user name such that it will pass
117+
// validation by UserRealNameValid. This is done to avoid blocking
118+
// little Bobby Whitespace from using Coder.
119+
funcNormalizeRealUsername(strstring)string {
120+
s:=strings.TrimSpace(str)
121+
iflen(s)>128 {
122+
s=s[:128]
123+
}
124+
returns
125+
}

‎coderd/httpapi/name_test.gorenamed to‎codersdk/name_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
packagehttpapi_test
1+
packagecodersdk_test
22

33
import (
44
"strings"
@@ -7,7 +7,7 @@ import (
77
"github.com/stretchr/testify/assert"
88
"github.com/stretchr/testify/require"
99

10-
"github.com/coder/coder/v2/coderd/httpapi"
10+
"github.com/coder/coder/v2/codersdk"
1111
"github.com/coder/coder/v2/testutil"
1212
)
1313

@@ -62,7 +62,7 @@ func TestUsernameValid(t *testing.T) {
6262
testCase:=testCase
6363
t.Run(testCase.Username,func(t*testing.T) {
6464
t.Parallel()
65-
valid:=httpapi.NameValid(testCase.Username)
65+
valid:=codersdk.NameValid(testCase.Username)
6666
require.Equal(t,testCase.Valid,valid==nil)
6767
})
6868
}
@@ -117,7 +117,7 @@ func TestTemplateDisplayNameValid(t *testing.T) {
117117
testCase:=testCase
118118
t.Run(testCase.Name,func(t*testing.T) {
119119
t.Parallel()
120-
valid:=httpapi.DisplayNameValid(testCase.Name)
120+
valid:=codersdk.DisplayNameValid(testCase.Name)
121121
require.Equal(t,testCase.Valid,valid==nil)
122122
})
123123
}
@@ -158,7 +158,7 @@ func TestTemplateVersionNameValid(t *testing.T) {
158158
testCase:=testCase
159159
t.Run(testCase.Name,func(t*testing.T) {
160160
t.Parallel()
161-
valid:=httpapi.TemplateVersionNameValid(testCase.Name)
161+
valid:=codersdk.TemplateVersionNameValid(testCase.Name)
162162
require.Equal(t,testCase.Valid,valid==nil)
163163
})
164164
}
@@ -169,7 +169,7 @@ func TestGeneratedTemplateVersionNameValid(t *testing.T) {
169169

170170
fori:=0;i<1000;i++ {
171171
name:=testutil.GetRandomName(t)
172-
err:=httpapi.TemplateVersionNameValid(name)
172+
err:=codersdk.TemplateVersionNameValid(name)
173173
require.NoError(t,err,"invalid template version name: %s",name)
174174
}
175175
}
@@ -199,9 +199,9 @@ func TestFrom(t *testing.T) {
199199
testCase:=testCase
200200
t.Run(testCase.From,func(t*testing.T) {
201201
t.Parallel()
202-
converted:=httpapi.UsernameFrom(testCase.From)
202+
converted:=codersdk.UsernameFrom(testCase.From)
203203
t.Log(converted)
204-
valid:=httpapi.NameValid(converted)
204+
valid:=codersdk.NameValid(converted)
205205
require.True(t,valid==nil)
206206
iftestCase.Match=="" {
207207
require.NotEqual(t,testCase.From,converted)
@@ -245,9 +245,9 @@ func TestUserRealNameValid(t *testing.T) {
245245
testCase:=testCase
246246
t.Run(testCase.Name,func(t*testing.T) {
247247
t.Parallel()
248-
err:=httpapi.UserRealNameValid(testCase.Name)
249-
norm:=httpapi.NormalizeRealUsername(testCase.Name)
250-
normErr:=httpapi.UserRealNameValid(norm)
248+
err:=codersdk.UserRealNameValid(testCase.Name)
249+
norm:=codersdk.NormalizeRealUsername(testCase.Name)
250+
normErr:=codersdk.UserRealNameValid(norm)
251251
assert.NoError(t,normErr)
252252
assert.Equal(t,testCase.Valid,err==nil)
253253
assert.Equal(t,testCase.Valid,norm==testCase.Name,"invalid name should be different after normalization")

‎enterprise/coderd/roles.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ func validOrganizationRoleRequest(ctx context.Context, req codersdk.CustomRoleRe
266266
returnfalse
267267
}
268268

269-
iferr:=httpapi.NameValid(req.Name);err!=nil {
269+
iferr:=codersdk.NameValid(req.Name);err!=nil {
270270
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
271271
Message:"Invalid role name",
272272
Detail:err.Error(),

‎enterprise/coderd/scim.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,15 +206,15 @@ func (api *API) scimPostUser(rw http.ResponseWriter, r *http.Request) {
206206
// The username is a required property in Coder. We make a best-effort
207207
// attempt at using what the claims provide, but if that fails we will
208208
// generate a random username.
209-
usernameValid:=httpapi.NameValid(sUser.UserName)
209+
usernameValid:=codersdk.NameValid(sUser.UserName)
210210
ifusernameValid!=nil {
211211
// If no username is provided, we can default to use the email address.
212212
// This will be converted in the from function below, so it's safe
213213
// to keep the domain.
214214
ifsUser.UserName=="" {
215215
sUser.UserName=email
216216
}
217-
sUser.UserName=httpapi.UsernameFrom(sUser.UserName)
217+
sUser.UserName=codersdk.UsernameFrom(sUser.UserName)
218218
}
219219

220220
// TODO: This is a temporary solution that does not support multi-org

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp