Skip to content

Commit 97a73d6

Browse files
committed
Added create user
1 parent dbecac0 commit 97a73d6

File tree

11 files changed

+332
-27
lines changed

11 files changed

+332
-27
lines changed

cmd/server/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type CLI struct {
4545
LDAP struct {
4646
ldap.ObjectCommands
4747
ldap.AuthCommands
48+
ldap.UserCommands
4849
} `cmd:""`
4950

5051
VersionCommands

cmd/server/service.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,11 @@ func (cmd *ServiceRunCommand) Run(app server.Cmd) error {
211211
ldap.Router = router
212212
}
213213

214+
// HACK
215+
ldap.UserSchema.RDN = "cn=users,cn=accounts"
216+
ldap.UserSchema.Field = "uid"
217+
ldap.UserSchema.ObjectClasses = "top,inetOrgPerson,person,posixAccount"
218+
214219
return nil
215220
}))
216221

pkg/ldap/client/user.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package client
2+
3+
import (
4+
"context"
5+
6+
// Packages
7+
client "github.com/mutablelogic/go-client"
8+
schema "github.com/mutablelogic/go-server/pkg/ldap/schema"
9+
)
10+
11+
///////////////////////////////////////////////////////////////////////////////
12+
// PUBLIC METHODS
13+
14+
func (c *Client) CreateUser(ctx context.Context, meta schema.Object) (*schema.Object, error) {
15+
req, err := client.NewJSONRequest(meta)
16+
if err != nil {
17+
return nil, err
18+
}
19+
20+
// Perform request
21+
var response schema.Object
22+
if err := c.DoWithContext(ctx, req, &response, client.OptPath("user")); err != nil {
23+
return nil, err
24+
}
25+
26+
// Return the responses
27+
return &response, nil
28+
}

pkg/ldap/cmd/user.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/url"
7+
"strings"
8+
9+
// Packages
10+
server "github.com/mutablelogic/go-server"
11+
client "github.com/mutablelogic/go-server/pkg/ldap/client"
12+
schema "github.com/mutablelogic/go-server/pkg/ldap/schema"
13+
)
14+
15+
///////////////////////////////////////////////////////////////////////////////
16+
// TYPES
17+
18+
type UserCommands struct {
19+
CreateUser UserCreateCommand `cmd:"" group:"LDAP" help:"Create user"`
20+
}
21+
22+
type UserGetCommand struct {
23+
DN string `arg:"" help:"Username"`
24+
}
25+
26+
type UserCreateCommand struct {
27+
UserGetCommand
28+
Attr []string `arg:"" help:"attribute=value,value,..."`
29+
}
30+
31+
///////////////////////////////////////////////////////////////////////////////
32+
// PUBLIC METHODS
33+
34+
func (cmd UserCreateCommand) Run(ctx server.Cmd) error {
35+
return run(ctx, func(ctx context.Context, provider *client.Client) error {
36+
// Decode attributes
37+
attrs := url.Values{}
38+
for _, attr := range cmd.Attr {
39+
parts := strings.SplitN(attr, "=", 2)
40+
if len(parts) != 2 {
41+
return fmt.Errorf("invalid attribute: %s", attr)
42+
}
43+
name := parts[0]
44+
if values := strings.Split(parts[1], ","); len(values) > 0 {
45+
attrs[name] = values
46+
}
47+
}
48+
49+
// Create user
50+
user, err := provider.CreateUser(ctx, schema.Object{
51+
DN: cmd.DN,
52+
Values: attrs,
53+
})
54+
if err != nil {
55+
return err
56+
}
57+
58+
// Print object
59+
fmt.Println(user)
60+
return nil
61+
})
62+
}

pkg/ldap/config/config.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package config
33
import (
44
"context"
55
"net/url"
6+
"strings"
67

78
// Packages
89
server "github.com/mutablelogic/go-server"
@@ -21,6 +22,11 @@ type Config struct {
2122
BaseDN string `env:"LDAP_BASE_DN" help:"Base DN"` // Base DN
2223
SkipVerify bool `env:"LDAP_SKIPVERIFY" help:"Skip TLS certificate verify"` // Skip verify
2324
Router server.HTTPRouter `kong:"-"` // HTTP Router
25+
UserSchema struct {
26+
RDN string `default:"cn=users,cn=account" help:"User root DN"`
27+
Field string `default:"uid" help:"User field"`
28+
ObjectClasses string `default:"top,inetOrgPerson,posixAccount" help:"User object classes"`
29+
}
2430
}
2531

2632
var _ server.Plugin = Config{}
@@ -44,6 +50,10 @@ func (c Config) New(ctx context.Context) (server.Task, error) {
4450
if c.SkipVerify {
4551
opts = append(opts, ldap.WithSkipVerify())
4652
}
53+
if c.UserSchema.RDN != "" {
54+
classes := strings.Split(c.UserSchema.ObjectClasses, ",")
55+
opts = append(opts, ldap.WithUserSchema(c.UserSchema.RDN, c.UserSchema.Field, classes...))
56+
}
4757

4858
// Create a new LDAP manager
4959
manager, err := ldap.NewManager(opts...)

pkg/ldap/handler/handler.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
func Register(ctx context.Context, router server.HTTPRouter, manager *ldap.Manager) {
1919
registerObject(ctx, router, schema.APIPrefix, manager)
2020
registerAuth(ctx, router, schema.APIPrefix, manager)
21+
registerUser(ctx, router, schema.APIPrefix, manager)
2122
}
2223

2324
func registerObject(ctx context.Context, router server.HTTPRouter, prefix string, manager *ldap.Manager) {
@@ -73,3 +74,17 @@ func registerAuth(ctx context.Context, router server.HTTPRouter, prefix string,
7374
}
7475
})
7576
}
77+
78+
func registerUser(ctx context.Context, router server.HTTPRouter, prefix string, manager *ldap.Manager) {
79+
router.HandleFunc(ctx, types.JoinPath(prefix, "user"), func(w http.ResponseWriter, r *http.Request) {
80+
defer r.Body.Close()
81+
httpresponse.Cors(w, r, router.Origin(), http.MethodPost)
82+
83+
switch r.Method {
84+
case http.MethodPost:
85+
_ = userCreate(w, r, manager)
86+
default:
87+
_ = httpresponse.Error(w, httpresponse.Err(http.StatusMethodNotAllowed), r.Method)
88+
}
89+
})
90+
}

pkg/ldap/handler/user.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package handler
2+
3+
import (
4+
"net/http"
5+
6+
// Packages
7+
httprequest "github.com/mutablelogic/go-server/pkg/httprequest"
8+
httpresponse "github.com/mutablelogic/go-server/pkg/httpresponse"
9+
ldap "github.com/mutablelogic/go-server/pkg/ldap"
10+
schema "github.com/mutablelogic/go-server/pkg/ldap/schema"
11+
)
12+
13+
///////////////////////////////////////////////////////////////////////////////
14+
// PRIVATE METHODS
15+
16+
func userCreate(w http.ResponseWriter, r *http.Request, manager *ldap.Manager) error {
17+
// Parse request
18+
var req schema.Object
19+
if err := httprequest.Read(r, &req); err != nil {
20+
return httpresponse.Error(w, err)
21+
}
22+
23+
// Create the object
24+
response, err := manager.CreateUser(r.Context(), req.DN, req.Values)
25+
if err != nil {
26+
return httpresponse.Error(w, err)
27+
}
28+
29+
// Return success
30+
return httpresponse.JSON(w, http.StatusCreated, httprequest.Indent(r), response)
31+
}

pkg/ldap/manager.go

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515

1616
// Packages
1717
ldap "github.com/go-ldap/ldap/v3"
18-
"github.com/mutablelogic/go-server"
18+
server "github.com/mutablelogic/go-server"
1919
httpresponse "github.com/mutablelogic/go-server/pkg/httpresponse"
2020
schema "github.com/mutablelogic/go-server/pkg/ldap/schema"
2121
ref "github.com/mutablelogic/go-server/pkg/ref"
@@ -33,8 +33,8 @@ type Manager struct {
3333
user, pass string
3434
dn *schema.DN
3535
conn *ldap.Conn
36-
users *schema.Group
37-
groups *schema.Group
36+
users *schema.ObjectType
37+
groups *schema.ObjectType
3838
}
3939

4040
var _ server.LDAP = (*Manager)(nil)
@@ -103,7 +103,7 @@ func NewManager(opt ...Opt) (*Manager, error) {
103103
self.dn = o.dn
104104
}
105105

106-
// Set the schemas for users, groups
106+
// Set the object types for users, groups
107107
self.users = o.users
108108
self.groups = o.groups
109109

@@ -637,6 +637,73 @@ func (manager *Manager) ListAttributeTypes(ctx context.Context) ([]*schema.Attri
637637
///////////////////////////////////////////////////////////////////////////////
638638
// PUBLIC METHODS - USERS AND GROUPS
639639

640+
// Create a user
641+
func (manager *Manager) CreateUser(ctx context.Context, user string, attrs url.Values) (*schema.Object, error) {
642+
if manager.users == nil {
643+
return nil, httpresponse.ErrBadRequest.With("User schema not set")
644+
}
645+
646+
// Create the object template
647+
object, err := manager.users.New(user, attrs)
648+
if err != nil {
649+
return nil, httpresponse.ErrBadRequest.With(err)
650+
}
651+
652+
// Make absolute DN
653+
absdn, err := manager.absdn(object.DN)
654+
if err != nil {
655+
return nil, err
656+
}
657+
658+
// TODO: Uid
659+
fmt.Println(absdn, object)
660+
661+
// Create the user
662+
return manager.Create(ctx, absdn.String(), object.Values)
663+
664+
// TODO: Groups
665+
}
666+
667+
/*
668+
// If the uid is not set, then set it to the next available uid
669+
var nextId int
670+
uid, err := ldap.SearchOne("(&(objectclass=device)(cn=lastuid))")
671+
if err != nil {
672+
return nil, err
673+
} else if uid == nil {
674+
return nil, ErrNotImplemented.With("lastuid not found")
675+
} else if uid_, err := strconv.ParseInt(uid.Get("serialNumber"), 10, 32); err != nil {
676+
return nil, ErrNotImplemented.With("lastuid not found")
677+
} else {
678+
nextId = int(uid_) + 1
679+
if err := schema.OptUserId(int(uid_))(o); err != nil {
680+
return nil, err
681+
}
682+
}
683+
684+
// Create the request
685+
addReq := goldap.NewAddRequest(o.DN, []goldap.Control{})
686+
for name, values := range o.Values {
687+
addReq.Attribute(name, values)
688+
}
689+
690+
// Request -> Response
691+
if err := ldap.conn.Add(addReq); err != nil {
692+
return nil, err
693+
}
694+
695+
// Increment the uid
696+
if uid != nil && nextId > 0 {
697+
modify := goldap.NewModifyRequest(uid.DN, []goldap.Control{})
698+
modify.Replace("serialNumber", []string{fmt.Sprint(nextId)})
699+
if err := ldap.conn.Modify(modify); err != nil {
700+
return nil, err
701+
}
702+
}
703+
704+
// TODO: Add the user to a group
705+
*/
706+
640707
// Return all users
641708
func (manager *Manager) ListUsers(ctx context.Context, request schema.ObjectListRequest) ([]*schema.ObjectList, error) {
642709
// TODO
@@ -661,12 +728,6 @@ func (manager *Manager) GetGroup(ctx context.Context, dn string) (*schema.Object
661728
return nil, httpresponse.ErrNotImplemented.With("GetGroup not implemented")
662729
}
663730

664-
// Create a user
665-
func (manager *Manager) CreateUser(ctx context.Context, user string, attrs url.Values) (*schema.Object, error) {
666-
// TODO
667-
return nil, httpresponse.ErrNotImplemented.With("CreateUser not implemented")
668-
}
669-
670731
// Create a group
671732
func (manager *Manager) CreateGroup(ctx context.Context, group string, attrs url.Values) (*schema.Object, error) {
672733
// TODO

pkg/ldap/opt.go

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ type opt struct {
1717
pass string
1818
dn *schema.DN
1919
skipverify bool
20-
users *schema.Group
21-
groups *schema.Group
20+
users *schema.ObjectType
21+
groups *schema.ObjectType
2222
}
2323

2424
// Opt represents a function that modifies the options
@@ -44,27 +44,23 @@ func applyOpts(opts ...Opt) (*opt, error) {
4444
////////////////////////////////////////////////////////////////////////////////
4545
// PUBLIC METHODS
4646

47-
func WithUserSchema(dn string, classes ...string) Opt {
47+
func WithUserSchema(dn, field string, classes ...string) Opt {
4848
return func(o *opt) error {
49-
if dn == "" {
50-
return httpresponse.ErrBadRequest.With("DN is empty")
51-
} else if bdn, err := schema.NewDN(dn); err != nil {
52-
return httpresponse.ErrBadRequest.With("DN is invalid: ", err)
49+
if ot, err := schema.NewObjectType(dn, field, classes...); err != nil {
50+
return err
5351
} else {
54-
o.users = &schema.Group{DN: bdn, ObjectClass: classes}
52+
o.users = ot
5553
}
5654
return nil
5755
}
5856
}
5957

60-
func WithGroupSchema(dn string, classes ...string) Opt {
58+
func WithGroupSchema(dn, field string, classes ...string) Opt {
6159
return func(o *opt) error {
62-
if dn == "" {
63-
return httpresponse.ErrBadRequest.With("DN is empty")
64-
} else if bdn, err := schema.NewDN(dn); err != nil {
65-
return httpresponse.ErrBadRequest.With("DN is invalid: ", err)
60+
if ot, err := schema.NewObjectType(dn, field, classes...); err != nil {
61+
return err
6662
} else {
67-
o.groups = &schema.Group{DN: bdn, ObjectClass: classes}
63+
o.groups = ot
6864
}
6965
return nil
7066
}

pkg/ldap/schema/object.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,32 @@ func (o *Object) GetAll(attr string) []string {
105105
// Not found
106106
return nil
107107
}
108+
109+
// Sets an attribute value
110+
func (o *Object) Set(attr string, v ...string) {
111+
// Function to add or delete an attribute
112+
set := func(k string, v []string) {
113+
if len(v) == 0 {
114+
delete(o.Values, k)
115+
} else {
116+
o.Values[k] = v
117+
}
118+
}
119+
120+
// Try case insensitive
121+
if values, ok := o.Values[attr]; ok {
122+
set(attr, values)
123+
return
124+
}
125+
126+
// Try case insensitive
127+
for k, values := range o.Values {
128+
if strings.EqualFold(k, attr) {
129+
set(attr, values)
130+
return
131+
}
132+
}
133+
134+
// Not found - add
135+
set(attr, v)
136+
}

0 commit comments

Comments
 (0)