Forráskód Böngészése

feat(user): implement update user feature

- Update user implementation.
- User test cases.
- Cli updates to utilize the new user update features.

Closes #23.
Mustafa Arici 8 éve
szülő
commit
b306ddebf9
8 módosított fájl, 329 hozzáadás és 45 törlés
  1. 32 0
      api/rpc.go
  2. 5 5
      bindata/bindata.go
  3. 81 2
      cmd/ovpm/main.go
  4. 128 31
      pb/user.pb.go
  5. 13 0
      pb/user.proto
  6. 31 7
      user.go
  7. 37 0
      user_test.go
  8. 2 0
      vpn_test.go

+ 32 - 0
api/rpc.go

@@ -46,12 +46,44 @@ func (s *UserService) Create(ctx context.Context, req *pb.UserCreateRequest) (*p
 	pbUser := pb.UserResponse_User{
 		Username:           user.GetUsername(),
 		ServerSerialNumber: user.GetServerSerialNumber(),
+		NoGW:               user.IsNoGW(),
 	}
 	ut = append(ut, &pbUser)
 
 	return &pb.UserResponse{Users: ut}, nil
 }
 
+func (s *UserService) Update(ctx context.Context, req *pb.UserUpdateRequest) (*pb.UserResponse, error) {
+	logrus.Debugf("rpc call: user update: %s", req.Username)
+	var ut []*pb.UserResponse_User
+	user, err := ovpm.GetUser(req.Username)
+	if err != nil {
+		return nil, err
+	}
+	var noGW bool
+
+	switch req.Gwpref {
+	case pb.UserUpdateRequest_NOGW:
+		noGW = false
+	case pb.UserUpdateRequest_GW:
+		noGW = true
+	default:
+		noGW = user.NoGW
+
+	}
+
+	user.Update(req.Password, noGW)
+	pbUser := pb.UserResponse_User{
+		Username:           user.GetUsername(),
+		ServerSerialNumber: user.GetServerSerialNumber(),
+		NoGW:               user.IsNoGW(),
+	}
+
+	ut = append(ut, &pbUser)
+
+	return &pb.UserResponse{Users: ut}, nil
+}
+
 func (s *UserService) Delete(ctx context.Context, req *pb.UserDeleteRequest) (*pb.UserResponse, error) {
 	logrus.Debugf("rpc call: user delete: %s", req.Username)
 	var ut []*pb.UserResponse_User

+ 5 - 5
bindata/bindata.go

@@ -87,7 +87,7 @@ func templateCcdFileTmpl() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindataFileInfo{name: "template/ccd.file.tmpl", size: 74, mode: os.FileMode(436), modTime: time.Unix(1502659670, 0)}
+	info := bindataFileInfo{name: "template/ccd.file.tmpl", size: 74, mode: os.FileMode(420), modTime: time.Unix(1502998813, 0)}
 	a := &asset{bytes: bytes, info: info}
 	return a, nil
 }
@@ -107,7 +107,7 @@ func templateClientOvpnTmpl() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindataFileInfo{name: "template/client.ovpn.tmpl", size: 365, mode: os.FileMode(436), modTime: time.Unix(1503043866, 0)}
+	info := bindataFileInfo{name: "template/client.ovpn.tmpl", size: 365, mode: os.FileMode(420), modTime: time.Unix(1503002198, 0)}
 	a := &asset{bytes: bytes, info: info}
 	return a, nil
 }
@@ -127,7 +127,7 @@ func templateDh4096PemTmpl() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindataFileInfo{name: "template/dh4096.pem.tmpl", size: 1468, mode: os.FileMode(436), modTime: time.Unix(1502659670, 0)}
+	info := bindataFileInfo{name: "template/dh4096.pem.tmpl", size: 1468, mode: os.FileMode(420), modTime: time.Unix(1502796579, 0)}
 	a := &asset{bytes: bytes, info: info}
 	return a, nil
 }
@@ -147,7 +147,7 @@ func templateIptablesTmpl() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindataFileInfo{name: "template/iptables.tmpl", size: 0, mode: os.FileMode(436), modTime: time.Unix(1502659670, 0)}
+	info := bindataFileInfo{name: "template/iptables.tmpl", size: 0, mode: os.FileMode(420), modTime: time.Unix(1502796579, 0)}
 	a := &asset{bytes: bytes, info: info}
 	return a, nil
 }
@@ -167,7 +167,7 @@ func templateServerConfTmpl() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindataFileInfo{name: "template/server.conf.tmpl", size: 9585, mode: os.FileMode(436), modTime: time.Unix(1502659670, 0)}
+	info := bindataFileInfo{name: "template/server.conf.tmpl", size: 9585, mode: os.FileMode(420), modTime: time.Unix(1502796579, 0)}
 	a := &asset{bytes: bytes, info: info}
 	return a, nil
 }

+ 81 - 2
cmd/ovpm/main.go

@@ -89,7 +89,7 @@ func main() {
 							Name:  "password, p",
 							Usage: "password for the vpn user",
 						},
-						cli.StringFlag{
+						cli.BoolFlag{
 							Name:  "no-gw",
 							Usage: "don't push vpn server as default gateway for this user",
 						},
@@ -98,6 +98,7 @@ func main() {
 						action = "user:create"
 						username := c.String("username")
 						password := c.String("password")
+						noGW := c.Bool("no-gw")
 
 						if username == "" || password == "" {
 							fmt.Println(cli.ShowSubcommandHelp(c))
@@ -109,7 +110,7 @@ func main() {
 						defer conn.Close()
 						userSvc := pb.NewUserServiceClient(conn)
 
-						response, err := userSvc.Create(context.Background(), &pb.UserCreateRequest{Username: username, Password: password})
+						response, err := userSvc.Create(context.Background(), &pb.UserCreateRequest{Username: username, Password: password, NoGW: noGW})
 						if err != nil {
 							logrus.Errorf("user can not be created '%s': %v", username, err)
 							os.Exit(1)
@@ -119,6 +120,84 @@ func main() {
 						return nil
 					},
 				},
+				{
+					Name:  "update",
+					Usage: "Update a VPN user.",
+					Flags: []cli.Flag{
+						cli.StringFlag{
+							Name:  "username, u",
+							Usage: "username of the vpn user to update",
+						},
+						cli.StringFlag{
+							Name:  "password, p",
+							Usage: "new password for the vpn user",
+						},
+						cli.BoolFlag{
+							Name:  "no-gw",
+							Usage: "don't push vpn server as default gateway for this user",
+						},
+						cli.BoolFlag{
+							Name:  "gw",
+							Usage: "push vpn server as default gateway for this user",
+						},
+					},
+					Action: func(c *cli.Context) error {
+						action = "user:update"
+						username := c.String("username")
+						password := c.String("password")
+						nogw := c.Bool("no-gw")
+						gw := c.Bool("gw")
+
+						if username == "" {
+							fmt.Println(cli.ShowSubcommandHelp(c))
+							os.Exit(1)
+						}
+
+						if !(password != "" || gw || nogw) {
+							fmt.Println("nothing is updated!")
+							fmt.Println()
+							fmt.Println(cli.ShowSubcommandHelp(c))
+							os.Exit(1)
+						}
+
+						var gwPref pb.UserUpdateRequest_GWPref
+
+						switch {
+						case gw && !nogw:
+							gwPref = pb.UserUpdateRequest_GW
+						case !gw && nogw:
+							gwPref = pb.UserUpdateRequest_NOGW
+						case gw && nogw:
+							// Ambigius.
+							fmt.Println("you can't use --gw together with --no-gw")
+							fmt.Println()
+							fmt.Println(cli.ShowSubcommandHelp(c))
+							os.Exit(1)
+						default:
+							gwPref = pb.UserUpdateRequest_NOPREF
+
+						}
+
+						//conn := getConn(c.String("port"))
+						conn := getConn(c.GlobalString("daemon-port"))
+						defer conn.Close()
+						userSvc := pb.NewUserServiceClient(conn)
+
+						response, err := userSvc.Update(context.Background(), &pb.UserUpdateRequest{
+							Username: username,
+							Password: password,
+							Gwpref:   gwPref,
+						})
+
+						if err != nil {
+							logrus.Errorf("user can not be updated '%s': %v", username, err)
+							os.Exit(1)
+							return err
+						}
+						logrus.Infof("user updated: %s", response.Users[0].Username)
+						return nil
+					},
+				},
 				{
 					Name:  "delete",
 					Usage: "Delete a VPN user.",

+ 128 - 31
pb/user.pb.go

@@ -11,6 +11,7 @@ It is generated from these files:
 It has these top-level messages:
 	UserListRequest
 	UserCreateRequest
+	UserUpdateRequest
 	UserDeleteRequest
 	UserRenewRequest
 	UserGenConfigRequest
@@ -43,6 +44,30 @@ var _ = math.Inf
 // proto package needs to be updated.
 const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
 
+type UserUpdateRequest_GWPref int32
+
+const (
+	UserUpdateRequest_NOPREF UserUpdateRequest_GWPref = 0
+	UserUpdateRequest_NOGW   UserUpdateRequest_GWPref = 1
+	UserUpdateRequest_GW     UserUpdateRequest_GWPref = 2
+)
+
+var UserUpdateRequest_GWPref_name = map[int32]string{
+	0: "NOPREF",
+	1: "NOGW",
+	2: "GW",
+}
+var UserUpdateRequest_GWPref_value = map[string]int32{
+	"NOPREF": 0,
+	"NOGW":   1,
+	"GW":     2,
+}
+
+func (x UserUpdateRequest_GWPref) String() string {
+	return proto.EnumName(UserUpdateRequest_GWPref_name, int32(x))
+}
+func (UserUpdateRequest_GWPref) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{2, 0} }
+
 type UserListRequest struct {
 }
 
@@ -83,6 +108,38 @@ func (m *UserCreateRequest) GetNoGW() bool {
 	return false
 }
 
+type UserUpdateRequest struct {
+	Username string                   `protobuf:"bytes,1,opt,name=Username" json:"Username,omitempty"`
+	Password string                   `protobuf:"bytes,2,opt,name=Password" json:"Password,omitempty"`
+	Gwpref   UserUpdateRequest_GWPref `protobuf:"varint,3,opt,name=gwpref,enum=pb.UserUpdateRequest_GWPref" json:"gwpref,omitempty"`
+}
+
+func (m *UserUpdateRequest) Reset()                    { *m = UserUpdateRequest{} }
+func (m *UserUpdateRequest) String() string            { return proto.CompactTextString(m) }
+func (*UserUpdateRequest) ProtoMessage()               {}
+func (*UserUpdateRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
+
+func (m *UserUpdateRequest) GetUsername() string {
+	if m != nil {
+		return m.Username
+	}
+	return ""
+}
+
+func (m *UserUpdateRequest) GetPassword() string {
+	if m != nil {
+		return m.Password
+	}
+	return ""
+}
+
+func (m *UserUpdateRequest) GetGwpref() UserUpdateRequest_GWPref {
+	if m != nil {
+		return m.Gwpref
+	}
+	return UserUpdateRequest_NOPREF
+}
+
 type UserDeleteRequest struct {
 	Username string `protobuf:"bytes,1,opt,name=Username" json:"Username,omitempty"`
 }
@@ -90,7 +147,7 @@ type UserDeleteRequest struct {
 func (m *UserDeleteRequest) Reset()                    { *m = UserDeleteRequest{} }
 func (m *UserDeleteRequest) String() string            { return proto.CompactTextString(m) }
 func (*UserDeleteRequest) ProtoMessage()               {}
-func (*UserDeleteRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
+func (*UserDeleteRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
 
 func (m *UserDeleteRequest) GetUsername() string {
 	if m != nil {
@@ -106,7 +163,7 @@ type UserRenewRequest struct {
 func (m *UserRenewRequest) Reset()                    { *m = UserRenewRequest{} }
 func (m *UserRenewRequest) String() string            { return proto.CompactTextString(m) }
 func (*UserRenewRequest) ProtoMessage()               {}
-func (*UserRenewRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
+func (*UserRenewRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
 
 func (m *UserRenewRequest) GetUsername() string {
 	if m != nil {
@@ -122,7 +179,7 @@ type UserGenConfigRequest struct {
 func (m *UserGenConfigRequest) Reset()                    { *m = UserGenConfigRequest{} }
 func (m *UserGenConfigRequest) String() string            { return proto.CompactTextString(m) }
 func (*UserGenConfigRequest) ProtoMessage()               {}
-func (*UserGenConfigRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
+func (*UserGenConfigRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
 
 func (m *UserGenConfigRequest) GetUsername() string {
 	if m != nil {
@@ -138,7 +195,7 @@ type UserResponse struct {
 func (m *UserResponse) Reset()                    { *m = UserResponse{} }
 func (m *UserResponse) String() string            { return proto.CompactTextString(m) }
 func (*UserResponse) ProtoMessage()               {}
-func (*UserResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
+func (*UserResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} }
 
 func (m *UserResponse) GetUsers() []*UserResponse_User {
 	if m != nil {
@@ -159,7 +216,7 @@ type UserResponse_User struct {
 func (m *UserResponse_User) Reset()                    { *m = UserResponse_User{} }
 func (m *UserResponse_User) String() string            { return proto.CompactTextString(m) }
 func (*UserResponse_User) ProtoMessage()               {}
-func (*UserResponse_User) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5, 0} }
+func (*UserResponse_User) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6, 0} }
 
 func (m *UserResponse_User) GetUsername() string {
 	if m != nil {
@@ -210,7 +267,7 @@ type UserGenConfigResponse struct {
 func (m *UserGenConfigResponse) Reset()                    { *m = UserGenConfigResponse{} }
 func (m *UserGenConfigResponse) String() string            { return proto.CompactTextString(m) }
 func (*UserGenConfigResponse) ProtoMessage()               {}
-func (*UserGenConfigResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} }
+func (*UserGenConfigResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} }
 
 func (m *UserGenConfigResponse) GetClientConfig() string {
 	if m != nil {
@@ -222,12 +279,14 @@ func (m *UserGenConfigResponse) GetClientConfig() string {
 func init() {
 	proto.RegisterType((*UserListRequest)(nil), "pb.UserListRequest")
 	proto.RegisterType((*UserCreateRequest)(nil), "pb.UserCreateRequest")
+	proto.RegisterType((*UserUpdateRequest)(nil), "pb.UserUpdateRequest")
 	proto.RegisterType((*UserDeleteRequest)(nil), "pb.UserDeleteRequest")
 	proto.RegisterType((*UserRenewRequest)(nil), "pb.UserRenewRequest")
 	proto.RegisterType((*UserGenConfigRequest)(nil), "pb.UserGenConfigRequest")
 	proto.RegisterType((*UserResponse)(nil), "pb.UserResponse")
 	proto.RegisterType((*UserResponse_User)(nil), "pb.UserResponse.User")
 	proto.RegisterType((*UserGenConfigResponse)(nil), "pb.UserGenConfigResponse")
+	proto.RegisterEnum("pb.UserUpdateRequest_GWPref", UserUpdateRequest_GWPref_name, UserUpdateRequest_GWPref_value)
 }
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -243,6 +302,7 @@ const _ = grpc.SupportPackageIsVersion4
 type UserServiceClient interface {
 	List(ctx context.Context, in *UserListRequest, opts ...grpc.CallOption) (*UserResponse, error)
 	Create(ctx context.Context, in *UserCreateRequest, opts ...grpc.CallOption) (*UserResponse, error)
+	Update(ctx context.Context, in *UserUpdateRequest, opts ...grpc.CallOption) (*UserResponse, error)
 	Delete(ctx context.Context, in *UserDeleteRequest, opts ...grpc.CallOption) (*UserResponse, error)
 	Renew(ctx context.Context, in *UserRenewRequest, opts ...grpc.CallOption) (*UserResponse, error)
 	GenConfig(ctx context.Context, in *UserGenConfigRequest, opts ...grpc.CallOption) (*UserGenConfigResponse, error)
@@ -274,6 +334,15 @@ func (c *userServiceClient) Create(ctx context.Context, in *UserCreateRequest, o
 	return out, nil
 }
 
+func (c *userServiceClient) Update(ctx context.Context, in *UserUpdateRequest, opts ...grpc.CallOption) (*UserResponse, error) {
+	out := new(UserResponse)
+	err := grpc.Invoke(ctx, "/pb.UserService/Update", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
 func (c *userServiceClient) Delete(ctx context.Context, in *UserDeleteRequest, opts ...grpc.CallOption) (*UserResponse, error) {
 	out := new(UserResponse)
 	err := grpc.Invoke(ctx, "/pb.UserService/Delete", in, out, c.cc, opts...)
@@ -306,6 +375,7 @@ func (c *userServiceClient) GenConfig(ctx context.Context, in *UserGenConfigRequ
 type UserServiceServer interface {
 	List(context.Context, *UserListRequest) (*UserResponse, error)
 	Create(context.Context, *UserCreateRequest) (*UserResponse, error)
+	Update(context.Context, *UserUpdateRequest) (*UserResponse, error)
 	Delete(context.Context, *UserDeleteRequest) (*UserResponse, error)
 	Renew(context.Context, *UserRenewRequest) (*UserResponse, error)
 	GenConfig(context.Context, *UserGenConfigRequest) (*UserGenConfigResponse, error)
@@ -351,6 +421,24 @@ func _UserService_Create_Handler(srv interface{}, ctx context.Context, dec func(
 	return interceptor(ctx, in, info, handler)
 }
 
+func _UserService_Update_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(UserUpdateRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(UserServiceServer).Update(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/pb.UserService/Update",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(UserServiceServer).Update(ctx, req.(*UserUpdateRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
 func _UserService_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
 	in := new(UserDeleteRequest)
 	if err := dec(in); err != nil {
@@ -417,6 +505,10 @@ var _UserService_serviceDesc = grpc.ServiceDesc{
 			MethodName: "Create",
 			Handler:    _UserService_Create_Handler,
 		},
+		{
+			MethodName: "Update",
+			Handler:    _UserService_Update_Handler,
+		},
 		{
 			MethodName: "Delete",
 			Handler:    _UserService_Delete_Handler,
@@ -437,29 +529,34 @@ var _UserService_serviceDesc = grpc.ServiceDesc{
 func init() { proto.RegisterFile("user.proto", fileDescriptor0) }
 
 var fileDescriptor0 = []byte{
-	// 382 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0x5d, 0x6b, 0xea, 0x40,
-	0x10, 0xbd, 0xd1, 0x44, 0xcc, 0x28, 0x5c, 0x9d, 0xab, 0x90, 0x1b, 0xee, 0x83, 0xe4, 0x49, 0xb8,
-	0x10, 0xa9, 0x3e, 0xf6, 0xa9, 0x4d, 0x41, 0x0a, 0x45, 0x24, 0x52, 0xfa, 0x58, 0x92, 0x3a, 0x2d,
-	0x01, 0x4d, 0xd2, 0xdd, 0xb5, 0xfe, 0x9c, 0xfe, 0x83, 0xfe, 0xb2, 0xfe, 0x88, 0xb2, 0xd9, 0x7c,
-	0xa8, 0xa4, 0xc5, 0xb7, 0x99, 0x73, 0xe6, 0x64, 0x77, 0xcf, 0x9c, 0x00, 0xec, 0x38, 0x31, 0x37,
-	0x65, 0x89, 0x48, 0xb0, 0x91, 0x86, 0x4e, 0x1f, 0x7e, 0xdf, 0x73, 0x62, 0x77, 0x11, 0x17, 0x3e,
-	0xbd, 0xee, 0x88, 0x0b, 0xe7, 0x11, 0xfa, 0x12, 0xf2, 0x18, 0x05, 0x82, 0x72, 0x10, 0x6d, 0x68,
-	0x4b, 0x30, 0x0e, 0xb6, 0x64, 0x69, 0x23, 0x6d, 0x6c, 0xfa, 0x65, 0x2f, 0xb9, 0x65, 0xc0, 0xf9,
-	0x3e, 0x61, 0x6b, 0xab, 0xa1, 0xb8, 0xa2, 0x47, 0x04, 0x7d, 0x91, 0xcc, 0x1f, 0xac, 0xe6, 0x48,
-	0x1b, 0xb7, 0xfd, 0xac, 0x76, 0x26, 0xea, 0x80, 0x1b, 0xda, 0xd0, 0x59, 0x07, 0x38, 0x2e, 0xf4,
-	0x64, 0xed, 0x53, 0x4c, 0xfb, 0x73, 0xe6, 0xa7, 0x30, 0x90, 0xf5, 0x9c, 0x62, 0x2f, 0x89, 0x9f,
-	0xa3, 0x97, 0x73, 0x34, 0x9f, 0x1a, 0x74, 0xd5, 0x21, 0x3c, 0x4d, 0x62, 0x4e, 0xf8, 0x1f, 0x0c,
-	0xe9, 0x15, 0xb7, 0xb4, 0x51, 0x73, 0xdc, 0x99, 0x0e, 0xdd, 0x34, 0x74, 0x0f, 0x07, 0x54, 0xa3,
-	0x66, 0xec, 0x0f, 0x0d, 0x74, 0xd9, 0xff, 0xe8, 0x93, 0x0b, 0xb8, 0x22, 0xf6, 0x46, 0x6c, 0x45,
-	0x2c, 0x0a, 0x36, 0x8b, 0xdd, 0x36, 0x24, 0x96, 0x3b, 0x56, 0xc3, 0x48, 0xef, 0x3c, 0x62, 0x22,
-	0xf3, 0xce, 0xf4, 0xb3, 0x1a, 0xff, 0x81, 0xa9, 0x16, 0xb3, 0xbe, 0x12, 0x96, 0x9e, 0x11, 0x15,
-	0x80, 0x03, 0x30, 0x6e, 0x97, 0x0b, 0x12, 0x96, 0x91, 0x31, 0xaa, 0x29, 0x77, 0xd0, 0x3a, 0xd8,
-	0xc1, 0x25, 0x0c, 0x4f, 0x2c, 0xca, 0x9f, 0xed, 0x40, 0xd7, 0xdb, 0x44, 0x14, 0x0b, 0x85, 0xe7,
-	0x8f, 0x38, 0xc2, 0xa6, 0xef, 0x0d, 0xe8, 0x48, 0xb5, 0xbc, 0x73, 0xf4, 0x44, 0x38, 0x01, 0x5d,
-	0x06, 0x08, 0xff, 0x14, 0x1e, 0x1d, 0xc4, 0xc9, 0xee, 0x9d, 0x1a, 0xe7, 0xfc, 0xc2, 0x19, 0xb4,
-	0xd4, 0xa5, 0xb1, 0xb4, 0xf5, 0x28, 0x6e, 0xdf, 0x89, 0x54, 0x64, 0x2a, 0xd1, 0x51, 0x84, 0x6a,
-	0x45, 0x17, 0x60, 0x64, 0xb1, 0xc1, 0x41, 0x45, 0x56, 0x29, 0xaa, 0x95, 0x5c, 0x83, 0x59, 0xda,
-	0x82, 0x56, 0x31, 0x70, 0x1a, 0x26, 0xfb, 0x6f, 0x0d, 0x53, 0x7c, 0x23, 0x6c, 0x65, 0x7f, 0xd8,
-	0xec, 0x2b, 0x00, 0x00, 0xff, 0xff, 0xde, 0xf2, 0x7b, 0x7e, 0x6f, 0x03, 0x00, 0x00,
+	// 461 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0xc1, 0x6e, 0xd3, 0x40,
+	0x10, 0xed, 0xba, 0x8e, 0x95, 0x4c, 0x2b, 0x70, 0x87, 0x54, 0x32, 0x51, 0x0f, 0xd1, 0x1e, 0x50,
+	0x24, 0x24, 0x57, 0xa4, 0xdc, 0x38, 0x41, 0x00, 0x0b, 0x09, 0xb9, 0x91, 0xab, 0x2a, 0x47, 0x94,
+	0x90, 0x49, 0x65, 0x29, 0xb5, 0xcd, 0xee, 0x86, 0x7c, 0x13, 0x17, 0xbe, 0x86, 0xcf, 0xe0, 0x23,
+	0xd0, 0xee, 0xda, 0x71, 0x1d, 0x5c, 0x94, 0x03, 0xb7, 0x99, 0x79, 0xf3, 0x76, 0x66, 0xdf, 0x3e,
+	0x1b, 0x60, 0x23, 0x49, 0x84, 0x85, 0xc8, 0x55, 0x8e, 0x4e, 0xb1, 0xe0, 0x67, 0xf0, 0xf4, 0x56,
+	0x92, 0xf8, 0x9c, 0x4a, 0x95, 0xd0, 0xb7, 0x0d, 0x49, 0xc5, 0xbf, 0xc0, 0x99, 0x2e, 0x4d, 0x04,
+	0xcd, 0x15, 0x95, 0x45, 0x1c, 0x40, 0x57, 0x17, 0xb3, 0xf9, 0x3d, 0x05, 0x6c, 0xc8, 0x46, 0xbd,
+	0x64, 0x97, 0x6b, 0x6c, 0x3a, 0x97, 0x72, 0x9b, 0x8b, 0x65, 0xe0, 0x58, 0xac, 0xca, 0x11, 0xc1,
+	0x8d, 0xf3, 0x68, 0x16, 0x1c, 0x0f, 0xd9, 0xa8, 0x9b, 0x98, 0x98, 0xff, 0x60, 0x76, 0xc2, 0x6d,
+	0xb1, 0xfc, 0x0f, 0x13, 0x5e, 0x83, 0x77, 0xb7, 0x2d, 0x04, 0xad, 0xcc, 0x8c, 0x27, 0xe3, 0x8b,
+	0xb0, 0x58, 0x84, 0x7f, 0x1d, 0x1f, 0x46, 0xb3, 0xa9, 0xa0, 0x55, 0x52, 0xf6, 0xf2, 0x17, 0xe0,
+	0xd9, 0x0a, 0x02, 0x78, 0xf1, 0xf5, 0x34, 0xf9, 0xf0, 0xd1, 0x3f, 0xc2, 0x2e, 0xb8, 0xf1, 0x75,
+	0x34, 0xf3, 0x19, 0x7a, 0xe0, 0x44, 0x33, 0xdf, 0xe1, 0x97, 0x76, 0xd5, 0xf7, 0xb4, 0xa6, 0x83,
+	0x56, 0xe5, 0x21, 0xf8, 0x3a, 0x4e, 0x28, 0xa3, 0xed, 0x21, 0xfd, 0x63, 0xe8, 0xeb, 0x38, 0xa2,
+	0x6c, 0x92, 0x67, 0xab, 0xf4, 0xee, 0x10, 0xce, 0x6f, 0x06, 0xa7, 0x76, 0x88, 0x2c, 0xf2, 0x4c,
+	0x12, 0xbe, 0x84, 0x8e, 0x7e, 0x57, 0x19, 0xb0, 0xe1, 0xf1, 0xe8, 0x64, 0x7c, 0x5e, 0x49, 0x50,
+	0x35, 0xd8, 0xc4, 0xf6, 0x0c, 0x7e, 0x32, 0x70, 0x75, 0xfe, 0x4f, 0xc5, 0x43, 0xc0, 0x1b, 0x12,
+	0xdf, 0x49, 0xdc, 0x90, 0x48, 0xe7, 0xeb, 0x78, 0x73, 0xbf, 0x20, 0x51, 0x6a, 0xdf, 0x82, 0xe8,
+	0x77, 0x9e, 0x90, 0x50, 0xe6, 0x0d, 0x7a, 0x89, 0x89, 0xf1, 0x02, 0x7a, 0xd6, 0x44, 0xcb, 0xb7,
+	0x2a, 0x70, 0x0d, 0x50, 0x17, 0xb0, 0x0f, 0x9d, 0x4f, 0xd3, 0x98, 0x54, 0xd0, 0x31, 0x88, 0x4d,
+	0x76, 0x7e, 0xf1, 0x1e, 0xf8, 0xe5, 0x0d, 0x9c, 0xef, 0x49, 0x54, 0x5e, 0x9b, 0xc3, 0xe9, 0x64,
+	0x9d, 0x52, 0xa6, 0x6c, 0xbd, 0xbc, 0x44, 0xa3, 0x36, 0xfe, 0xe5, 0xc0, 0x89, 0x66, 0xeb, 0x9d,
+	0xd3, 0xaf, 0x84, 0x97, 0xe0, 0x6a, 0xb3, 0xe3, 0xb3, 0x4a, 0xa3, 0x07, 0xd6, 0x1f, 0xf8, 0xfb,
+	0xc2, 0xf1, 0x23, 0xbc, 0x02, 0xcf, 0x2e, 0x8d, 0x3b, 0x59, 0x1b, 0x9f, 0xc6, 0x63, 0x24, 0x6b,
+	0xbf, 0x9a, 0xd4, 0xb0, 0xe3, 0x63, 0x24, 0xeb, 0xb3, 0x9a, 0xd4, 0xf0, 0x5d, 0x2b, 0xe9, 0x15,
+	0x74, 0x8c, 0xd7, 0xb0, 0x5f, 0x83, 0xb5, 0xf5, 0x5a, 0x29, 0xef, 0xa0, 0xb7, 0xd3, 0x12, 0x83,
+	0xaa, 0x61, 0xdf, 0x81, 0x83, 0xe7, 0x2d, 0x48, 0x75, 0xc6, 0xc2, 0x33, 0xbf, 0x90, 0xab, 0x3f,
+	0x01, 0x00, 0x00, 0xff, 0xff, 0x9e, 0x2b, 0xc6, 0x91, 0x50, 0x04, 0x00, 0x00,
 }

+ 13 - 0
pb/user.proto

@@ -12,6 +12,18 @@ message UserCreateRequest {
   bool NoGW = 3;
 }
 
+message UserUpdateRequest {
+  string Username = 1;
+  string Password = 2;
+  enum GWPref {
+    NOPREF = 0;
+    NOGW = 1;
+    GW = 2;
+  }
+  GWPref gwpref = 3;
+}
+
+
 message UserDeleteRequest {
   string Username = 1;
 }
@@ -27,6 +39,7 @@ message UserGenConfigRequest {
 service UserService {
   rpc List (UserListRequest) returns (UserResponse) {}
   rpc Create (UserCreateRequest) returns (UserResponse) {}
+  rpc Update (UserUpdateRequest) returns (UserResponse) {}
   rpc Delete (UserDeleteRequest) returns (UserResponse) {}
   rpc Renew (UserRenewRequest) returns (UserResponse) {}
   rpc GenConfig (UserGenConfigRequest) returns (UserGenConfigResponse) {}

+ 31 - 7
user.go

@@ -5,11 +5,12 @@ import (
 	"net"
 	"time"
 
+	passlib "gopkg.in/hlandau/passlib.v1"
+
 	"github.com/Sirupsen/logrus"
 	"github.com/asaskevich/govalidator"
 	"github.com/cad/ovpm/pki"
 	"github.com/jinzhu/gorm"
-	"gopkg.in/hlandau/passlib.v1"
 )
 
 // User represents the interface that is being used within the public api.
@@ -28,10 +29,10 @@ type DBUser struct {
 	Server   DBServer
 
 	Username           string `gorm:"unique_index"`
-	Cert               string
-	ServerSerialNumber string
+	Cert               string // not user writable
+	ServerSerialNumber string // not user writable
 	Hash               string
-	Key                string
+	Key                string // not user writable
 	NoGW               bool
 }
 
@@ -134,9 +135,32 @@ func CreateNewUser(username, password string, nogw bool) (*DBUser, error) {
 	return &user, nil
 }
 
+// Update updates the user's attributes and writes them to the database.
+//
+// How this method works is similiar to PUT semantics of REST. It sets the user record fields to the provided function arguments.
+func (u *DBUser) Update(password string, nogw bool) error {
+	if !IsInitialized() {
+		return fmt.Errorf("you first need to create server")
+	}
+
+	// If password is provided; set it. If not; leave it as it is.
+	if password != "" {
+		u.setPassword(password)
+	}
+
+	u.NoGW = nogw
+	db.Save(u)
+
+	err := Emit()
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
 // Delete deletes a user by the given username from the database.
 func (u *DBUser) Delete() error {
-	if db.NewRecord(&u) {
+	if db.NewRecord(u) {
 		// user is not found
 		return fmt.Errorf("user is not initialized: %s", u.Username)
 	}
@@ -147,7 +171,7 @@ func (u *DBUser) Delete() error {
 	db.Create(&DBRevoked{
 		SerialNumber: crt.SerialNumber.Text(16),
 	})
-	db.Unscoped().Delete(&u)
+	db.Unscoped().Delete(u)
 	logrus.Infof("user deleted: %s", u.GetUsername())
 	err = Emit()
 	if err != nil {
@@ -201,7 +225,7 @@ func (u *DBUser) Renew() error {
 	u.Key = clientCert.Key
 	u.ServerSerialNumber = server.SerialNumber
 
-	db.Save(&u)
+	db.Save(u)
 	err = Emit()
 	if err != nil {
 		return err

+ 37 - 0
user_test.go

@@ -77,6 +77,43 @@ func TestCreateNewUser(t *testing.T) {
 	}
 }
 
+func TestUserUpdate(t *testing.T) {
+	// Initialize:
+	ovpm.Testing = true
+	ovpm.SetupDB("sqlite3", ":memory:")
+	defer ovpm.CeaseDB()
+	ovpm.Init("localhost", "")
+
+	// Prepare:
+	username := "testUser"
+	password := "testPasswd1234"
+	noGW := false
+
+	// Test:
+	user, err := ovpm.CreateNewUser(username, password, noGW)
+	if err != nil {
+		t.Fatalf("user can not be created: %v", err)
+	}
+
+	var updatetests = []struct {
+		password string
+		noGW     bool
+		ok       bool
+	}{
+		{"testpw", false, true},
+		{"123", false, true},
+		{"123", false, true},
+		{"", true, true},
+	}
+
+	for _, tt := range updatetests {
+		err := user.Update(tt.password, tt.noGW)
+		if (err == nil) != tt.ok {
+			t.Errorf("user is expected to be able to update but it gave us this error instead: %v", err)
+		}
+	}
+}
+
 func TestUserPasswordCorrect(t *testing.T) {
 	// Initialize:
 	ovpm.Testing = true

+ 2 - 0
vpn_test.go

@@ -443,7 +443,9 @@ func (f *fakeProcess) Status() supervisor.State {
 }
 
 func init() {
+	// Init
 	Testing = true
+	fs = make(map[string]string)
 	// Monkeypatch emitToFile()
 	monkey.Patch(emitToFile, func(path, content string, mode uint) error {
 		fs[path] = content