From ed7e9434063dd6c176eda860963e272067c5c3f8 Mon Sep 17 00:00:00 2001 From: rithu leena john Date: Thu, 27 Oct 2016 16:28:11 -0700 Subject: [PATCH] api: add gRPC endpoints for creating, updating and deleting passwords --- api/api.pb.go | 260 +++++++++++++++++++++++++++++++++++++++++---- api/api.proto | 49 +++++++++ server/api.go | 101 +++++++++++++++++- server/api_test.go | 58 ++++++++++ storage/storage.go | 3 +- 5 files changed, 444 insertions(+), 27 deletions(-) diff --git a/api/api.pb.go b/api/api.pb.go index 3c4d8f76..4543e62e 100644 --- a/api/api.pb.go +++ b/api/api.pb.go @@ -14,6 +14,13 @@ It has these top-level messages: CreateClientResp DeleteClientReq DeleteClientResp + Password + CreatePasswordReq + CreatePasswordResp + UpdatePasswordReq + UpdatePasswordResp + DeletePasswordReq + DeletePasswordResp */ package api @@ -109,12 +116,103 @@ func (m *DeleteClientResp) String() string { return proto.CompactText func (*DeleteClientResp) ProtoMessage() {} func (*DeleteClientResp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } +// Password is an email for password mapping managed by the storage. +type Password struct { + Email string `protobuf:"bytes,1,opt,name=email" json:"email,omitempty"` + // Currently we do not accept plain text passwords. Could be an option in the future. + Hash []byte `protobuf:"bytes,2,opt,name=hash,proto3" json:"hash,omitempty"` + Username string `protobuf:"bytes,3,opt,name=username" json:"username,omitempty"` + UserId string `protobuf:"bytes,4,opt,name=user_id,json=userId" json:"user_id,omitempty"` +} + +func (m *Password) Reset() { *m = Password{} } +func (m *Password) String() string { return proto.CompactTextString(m) } +func (*Password) ProtoMessage() {} +func (*Password) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } + +// CreatePasswordReq is a request to make a password. +type CreatePasswordReq struct { + Password *Password `protobuf:"bytes,1,opt,name=password" json:"password,omitempty"` +} + +func (m *CreatePasswordReq) Reset() { *m = CreatePasswordReq{} } +func (m *CreatePasswordReq) String() string { return proto.CompactTextString(m) } +func (*CreatePasswordReq) ProtoMessage() {} +func (*CreatePasswordReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } + +func (m *CreatePasswordReq) GetPassword() *Password { + if m != nil { + return m.Password + } + return nil +} + +// CreatePasswordResp returns the response from creating a password. +type CreatePasswordResp struct { + AlreadyExists bool `protobuf:"varint,1,opt,name=already_exists,json=alreadyExists" json:"already_exists,omitempty"` +} + +func (m *CreatePasswordResp) Reset() { *m = CreatePasswordResp{} } +func (m *CreatePasswordResp) String() string { return proto.CompactTextString(m) } +func (*CreatePasswordResp) ProtoMessage() {} +func (*CreatePasswordResp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } + +// UpdatePasswordReq is a request to modify an existing password. +type UpdatePasswordReq struct { + // The email used to lookup the password. This field cannot be modified + Email string `protobuf:"bytes,1,opt,name=email" json:"email,omitempty"` + NewHash []byte `protobuf:"bytes,2,opt,name=new_hash,json=newHash,proto3" json:"new_hash,omitempty"` + NewUsername string `protobuf:"bytes,3,opt,name=new_username,json=newUsername" json:"new_username,omitempty"` +} + +func (m *UpdatePasswordReq) Reset() { *m = UpdatePasswordReq{} } +func (m *UpdatePasswordReq) String() string { return proto.CompactTextString(m) } +func (*UpdatePasswordReq) ProtoMessage() {} +func (*UpdatePasswordReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} } + +// UpdatePasswordResp returns the response from modifying an existing password. +type UpdatePasswordResp struct { + NotFound bool `protobuf:"varint,1,opt,name=not_found,json=notFound" json:"not_found,omitempty"` +} + +func (m *UpdatePasswordResp) Reset() { *m = UpdatePasswordResp{} } +func (m *UpdatePasswordResp) String() string { return proto.CompactTextString(m) } +func (*UpdatePasswordResp) ProtoMessage() {} +func (*UpdatePasswordResp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} } + +// DeletePasswordReq is a request to delete a password. +type DeletePasswordReq struct { + Email string `protobuf:"bytes,1,opt,name=email" json:"email,omitempty"` +} + +func (m *DeletePasswordReq) Reset() { *m = DeletePasswordReq{} } +func (m *DeletePasswordReq) String() string { return proto.CompactTextString(m) } +func (*DeletePasswordReq) ProtoMessage() {} +func (*DeletePasswordReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} } + +// DeletePasswordResp returns the response from deleting a password. +type DeletePasswordResp struct { + NotFound bool `protobuf:"varint,1,opt,name=not_found,json=notFound" json:"not_found,omitempty"` +} + +func (m *DeletePasswordResp) Reset() { *m = DeletePasswordResp{} } +func (m *DeletePasswordResp) String() string { return proto.CompactTextString(m) } +func (*DeletePasswordResp) ProtoMessage() {} +func (*DeletePasswordResp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} } + func init() { proto.RegisterType((*Client)(nil), "api.Client") proto.RegisterType((*CreateClientReq)(nil), "api.CreateClientReq") proto.RegisterType((*CreateClientResp)(nil), "api.CreateClientResp") proto.RegisterType((*DeleteClientReq)(nil), "api.DeleteClientReq") proto.RegisterType((*DeleteClientResp)(nil), "api.DeleteClientResp") + proto.RegisterType((*Password)(nil), "api.Password") + proto.RegisterType((*CreatePasswordReq)(nil), "api.CreatePasswordReq") + proto.RegisterType((*CreatePasswordResp)(nil), "api.CreatePasswordResp") + proto.RegisterType((*UpdatePasswordReq)(nil), "api.UpdatePasswordReq") + proto.RegisterType((*UpdatePasswordResp)(nil), "api.UpdatePasswordResp") + proto.RegisterType((*DeletePasswordReq)(nil), "api.DeletePasswordReq") + proto.RegisterType((*DeletePasswordResp)(nil), "api.DeletePasswordResp") } // Reference imports to suppress errors if they are not otherwise used. @@ -132,6 +230,12 @@ type DexClient interface { CreateClient(ctx context.Context, in *CreateClientReq, opts ...grpc.CallOption) (*CreateClientResp, error) // DeleteClient attempts to delete the provided client. DeleteClient(ctx context.Context, in *DeleteClientReq, opts ...grpc.CallOption) (*DeleteClientResp, error) + // CreatePassword attempts to create the password. + CreatePassword(ctx context.Context, in *CreatePasswordReq, opts ...grpc.CallOption) (*CreatePasswordResp, error) + // UpdatePassword attempts to modify existing password. + UpdatePassword(ctx context.Context, in *UpdatePasswordReq, opts ...grpc.CallOption) (*UpdatePasswordResp, error) + // DeletePassword attempts to delete the password. + DeletePassword(ctx context.Context, in *DeletePasswordReq, opts ...grpc.CallOption) (*DeletePasswordResp, error) } type dexClient struct { @@ -160,6 +264,33 @@ func (c *dexClient) DeleteClient(ctx context.Context, in *DeleteClientReq, opts return out, nil } +func (c *dexClient) CreatePassword(ctx context.Context, in *CreatePasswordReq, opts ...grpc.CallOption) (*CreatePasswordResp, error) { + out := new(CreatePasswordResp) + err := grpc.Invoke(ctx, "/api.Dex/CreatePassword", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *dexClient) UpdatePassword(ctx context.Context, in *UpdatePasswordReq, opts ...grpc.CallOption) (*UpdatePasswordResp, error) { + out := new(UpdatePasswordResp) + err := grpc.Invoke(ctx, "/api.Dex/UpdatePassword", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *dexClient) DeletePassword(ctx context.Context, in *DeletePasswordReq, opts ...grpc.CallOption) (*DeletePasswordResp, error) { + out := new(DeletePasswordResp) + err := grpc.Invoke(ctx, "/api.Dex/DeletePassword", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // Server API for Dex service type DexServer interface { @@ -167,6 +298,12 @@ type DexServer interface { CreateClient(context.Context, *CreateClientReq) (*CreateClientResp, error) // DeleteClient attempts to delete the provided client. DeleteClient(context.Context, *DeleteClientReq) (*DeleteClientResp, error) + // CreatePassword attempts to create the password. + CreatePassword(context.Context, *CreatePasswordReq) (*CreatePasswordResp, error) + // UpdatePassword attempts to modify existing password. + UpdatePassword(context.Context, *UpdatePasswordReq) (*UpdatePasswordResp, error) + // DeletePassword attempts to delete the password. + DeletePassword(context.Context, *DeletePasswordReq) (*DeletePasswordResp, error) } func RegisterDexServer(s *grpc.Server, srv DexServer) { @@ -209,6 +346,60 @@ func _Dex_DeleteClient_Handler(srv interface{}, ctx context.Context, dec func(in return interceptor(ctx, in, info, handler) } +func _Dex_CreatePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreatePasswordReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DexServer).CreatePassword(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/api.Dex/CreatePassword", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DexServer).CreatePassword(ctx, req.(*CreatePasswordReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Dex_UpdatePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdatePasswordReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DexServer).UpdatePassword(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/api.Dex/UpdatePassword", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DexServer).UpdatePassword(ctx, req.(*UpdatePasswordReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Dex_DeletePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeletePasswordReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DexServer).DeletePassword(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/api.Dex/DeletePassword", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DexServer).DeletePassword(ctx, req.(*DeletePasswordReq)) + } + return interceptor(ctx, in, info, handler) +} + var _Dex_serviceDesc = grpc.ServiceDesc{ ServiceName: "api.Dex", HandlerType: (*DexServer)(nil), @@ -221,6 +412,18 @@ var _Dex_serviceDesc = grpc.ServiceDesc{ MethodName: "DeleteClient", Handler: _Dex_DeleteClient_Handler, }, + { + MethodName: "CreatePassword", + Handler: _Dex_CreatePassword_Handler, + }, + { + MethodName: "UpdatePassword", + Handler: _Dex_UpdatePassword_Handler, + }, + { + MethodName: "DeletePassword", + Handler: _Dex_DeletePassword_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: fileDescriptor0, @@ -229,27 +432,38 @@ var _Dex_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("api/api.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 337 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x74, 0x92, 0x4f, 0x6b, 0xe3, 0x30, - 0x10, 0xc5, 0x63, 0x3b, 0xeb, 0x38, 0x93, 0xbf, 0x88, 0xdd, 0xc5, 0xbb, 0xbd, 0xa4, 0x0e, 0x85, - 0x9c, 0x12, 0x48, 0xa1, 0xb7, 0x9e, 0x92, 0xf6, 0x5c, 0x0c, 0xb9, 0xd6, 0x28, 0xf6, 0xb4, 0x08, - 0x54, 0x4b, 0x95, 0x64, 0x48, 0xcf, 0xfd, 0x64, 0xfd, 0x66, 0x45, 0x8a, 0x0a, 0x4e, 0x68, 0x6f, - 0x7e, 0x3f, 0xcd, 0xcc, 0x7b, 0x33, 0x18, 0x46, 0x54, 0xb2, 0x15, 0x95, 0x6c, 0x29, 0x95, 0x30, - 0x82, 0x44, 0x54, 0xb2, 0xec, 0x23, 0x80, 0x78, 0xc3, 0x19, 0xd6, 0x86, 0x8c, 0x21, 0x64, 0x55, - 0x1a, 0xcc, 0x82, 0x45, 0x3f, 0x0f, 0x59, 0x45, 0xfe, 0x42, 0xac, 0xb1, 0x54, 0x68, 0xd2, 0xd0, - 0x31, 0xaf, 0xc8, 0x1c, 0x46, 0x0a, 0x2b, 0xa6, 0xb0, 0x34, 0x45, 0xa3, 0x98, 0x4e, 0xa3, 0x59, - 0xb4, 0xe8, 0xe7, 0xc3, 0x2f, 0xb8, 0x53, 0x4c, 0xdb, 0x22, 0xa3, 0x1a, 0x6d, 0xb0, 0x2a, 0x24, - 0xa2, 0xd2, 0x69, 0xf7, 0x58, 0xe4, 0xe1, 0x83, 0x65, 0xd6, 0x41, 0x36, 0x7b, 0xce, 0xca, 0xf4, - 0xd7, 0x2c, 0x58, 0x24, 0xb9, 0x57, 0x84, 0x40, 0xb7, 0xa6, 0x2f, 0x98, 0xc6, 0xce, 0xd7, 0x7d, - 0x93, 0x7f, 0x90, 0x70, 0xf1, 0x2c, 0x8a, 0x46, 0xf1, 0xb4, 0xe7, 0x78, 0xcf, 0xea, 0x9d, 0xe2, - 0xd9, 0x0d, 0x4c, 0x36, 0x0a, 0xa9, 0xc1, 0xe3, 0x22, 0x39, 0xbe, 0x92, 0x39, 0xc4, 0xa5, 0x13, - 0x6e, 0x9f, 0xc1, 0x7a, 0xb0, 0xb4, 0x7b, 0xfb, 0x77, 0xff, 0x94, 0x3d, 0xc2, 0xf4, 0xb4, 0x4f, - 0x4b, 0x72, 0x05, 0x63, 0xca, 0x15, 0xd2, 0xea, 0xad, 0xc0, 0x03, 0xd3, 0x46, 0xbb, 0x01, 0x49, - 0x3e, 0xf2, 0xf4, 0xce, 0xc1, 0xd6, 0xfc, 0xf0, 0xe7, 0xf9, 0x97, 0x30, 0xd9, 0x22, 0xc7, 0x76, - 0xae, 0xb3, 0x1b, 0x67, 0x2b, 0x98, 0x9e, 0x96, 0x68, 0x49, 0x2e, 0xa0, 0x5f, 0x0b, 0x53, 0x3c, - 0x89, 0xa6, 0xae, 0xbc, 0x7b, 0x52, 0x0b, 0x73, 0x6f, 0xf5, 0xfa, 0x3d, 0x80, 0x68, 0x8b, 0x07, - 0x72, 0x0b, 0xc3, 0x76, 0x76, 0xf2, 0xfb, 0x18, 0xe0, 0xf4, 0x0c, 0xff, 0xff, 0x7c, 0x43, 0xb5, - 0xcc, 0x3a, 0xb6, 0xbd, 0xed, 0xeb, 0xdb, 0xcf, 0xd2, 0xfa, 0xf6, 0xf3, 0x80, 0x59, 0x67, 0x1f, - 0xbb, 0x3f, 0xe8, 0xfa, 0x33, 0x00, 0x00, 0xff, 0xff, 0xd1, 0x13, 0xbd, 0x1b, 0x52, 0x02, 0x00, - 0x00, + // 527 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x54, 0xcb, 0x6e, 0xdb, 0x30, + 0x10, 0x8c, 0x1f, 0xb1, 0xe5, 0xf5, 0x23, 0x31, 0x91, 0x26, 0x8a, 0x7b, 0x71, 0x18, 0x14, 0x70, + 0x2e, 0x09, 0x9a, 0x02, 0xbd, 0x14, 0xed, 0xc5, 0x69, 0xd1, 0xde, 0x02, 0x01, 0xbe, 0x56, 0x60, + 0xcc, 0x6d, 0x42, 0x40, 0x91, 0x58, 0x92, 0x82, 0xd3, 0xcf, 0xeb, 0x2f, 0xf4, 0x8b, 0x0a, 0x52, + 0xb4, 0x21, 0xc9, 0x2e, 0xdc, 0x9b, 0x66, 0xb8, 0x9c, 0xe5, 0xcc, 0x2e, 0x04, 0x43, 0x26, 0xc5, + 0x0d, 0x93, 0xe2, 0x5a, 0xaa, 0xcc, 0x64, 0xa4, 0xc5, 0xa4, 0xa0, 0xbf, 0x1b, 0xd0, 0x99, 0x27, + 0x02, 0x53, 0x43, 0x46, 0xd0, 0x14, 0x3c, 0x6c, 0x4c, 0x1b, 0xb3, 0x5e, 0xd4, 0x14, 0x9c, 0x9c, + 0x42, 0x47, 0xe3, 0x52, 0xa1, 0x09, 0x9b, 0x8e, 0xf3, 0x88, 0x5c, 0xc2, 0x50, 0x21, 0x17, 0x0a, + 0x97, 0x26, 0xce, 0x95, 0xd0, 0x61, 0x6b, 0xda, 0x9a, 0xf5, 0xa2, 0xc1, 0x9a, 0x5c, 0x28, 0xa1, + 0x6d, 0x91, 0x51, 0xb9, 0x36, 0xc8, 0x63, 0x89, 0xa8, 0x74, 0xd8, 0x2e, 0x8a, 0x3c, 0x79, 0x6f, + 0x39, 0xdb, 0x41, 0xe6, 0x0f, 0x89, 0x58, 0x86, 0x87, 0xd3, 0xc6, 0x2c, 0x88, 0x3c, 0x22, 0x04, + 0xda, 0x29, 0x7b, 0xc6, 0xb0, 0xe3, 0xfa, 0xba, 0x6f, 0x72, 0x0e, 0x41, 0x92, 0x3d, 0x66, 0x71, + 0xae, 0x92, 0xb0, 0xeb, 0xf8, 0xae, 0xc5, 0x0b, 0x95, 0xd0, 0xf7, 0x70, 0x34, 0x57, 0xc8, 0x0c, + 0x16, 0x46, 0x22, 0xfc, 0x49, 0x2e, 0xa1, 0xb3, 0x74, 0xc0, 0xf9, 0xe9, 0xdf, 0xf6, 0xaf, 0xad, + 0x6f, 0x7f, 0xee, 0x8f, 0xe8, 0x77, 0x38, 0xae, 0xde, 0xd3, 0x92, 0xbc, 0x81, 0x11, 0x4b, 0x14, + 0x32, 0xfe, 0x2b, 0xc6, 0x17, 0xa1, 0x8d, 0x76, 0x02, 0x41, 0x34, 0xf4, 0xec, 0x67, 0x47, 0x96, + 0xf4, 0x9b, 0xff, 0xd6, 0xbf, 0x80, 0xa3, 0x3b, 0x4c, 0xb0, 0xfc, 0xae, 0x5a, 0xc6, 0xf4, 0x06, + 0x8e, 0xab, 0x25, 0x5a, 0x92, 0xd7, 0xd0, 0x4b, 0x33, 0x13, 0xff, 0xc8, 0xf2, 0x94, 0xfb, 0xee, + 0x41, 0x9a, 0x99, 0x2f, 0x16, 0x53, 0x01, 0xc1, 0x3d, 0xd3, 0x7a, 0x95, 0x29, 0x4e, 0x4e, 0xe0, + 0x10, 0x9f, 0x99, 0x48, 0xbc, 0x5e, 0x01, 0x6c, 0x78, 0x4f, 0x4c, 0x3f, 0xb9, 0x87, 0x0d, 0x22, + 0xf7, 0x4d, 0x26, 0x10, 0xe4, 0x1a, 0x95, 0x0b, 0xb5, 0xe5, 0x8a, 0x37, 0x98, 0x9c, 0x41, 0xd7, + 0x7e, 0xc7, 0x82, 0x87, 0xed, 0x62, 0xce, 0x16, 0x7e, 0xe3, 0xf4, 0x13, 0x8c, 0x8b, 0x78, 0xd6, + 0x0d, 0xad, 0x81, 0x2b, 0x08, 0xa4, 0x87, 0x3e, 0xda, 0xa1, 0xb3, 0xbe, 0xa9, 0xd9, 0x1c, 0xd3, + 0x0f, 0x40, 0xea, 0xf7, 0xff, 0x3b, 0x60, 0xfa, 0x08, 0xe3, 0x85, 0xe4, 0xb5, 0xe6, 0xbb, 0x0d, + 0x9f, 0x43, 0x90, 0xe2, 0x2a, 0x2e, 0x99, 0xee, 0xa6, 0xb8, 0xfa, 0x6a, 0x7d, 0x5f, 0xc0, 0xc0, + 0x1e, 0xd5, 0xbc, 0xf7, 0x53, 0x5c, 0x2d, 0x3c, 0x45, 0xdf, 0x02, 0xa9, 0x37, 0xda, 0x37, 0x83, + 0x2b, 0x18, 0x17, 0x43, 0xdb, 0xfb, 0x36, 0xab, 0x5e, 0x2f, 0xdd, 0xa3, 0x7e, 0xfb, 0xa7, 0x09, + 0xad, 0x3b, 0x7c, 0x21, 0x1f, 0x61, 0x50, 0xde, 0x4e, 0x72, 0x52, 0xac, 0x58, 0x75, 0xd1, 0x27, + 0xaf, 0x76, 0xb0, 0x5a, 0xd2, 0x03, 0x7b, 0xbd, 0xbc, 0x59, 0xfe, 0x7a, 0x6d, 0x1f, 0xfd, 0xf5, + 0xfa, 0x0a, 0xd2, 0x03, 0x32, 0x87, 0x51, 0x75, 0x78, 0xe4, 0xb4, 0xd4, 0xa9, 0x64, 0x7c, 0x72, + 0xb6, 0x93, 0x5f, 0x8b, 0x54, 0xb3, 0xf5, 0x22, 0x5b, 0x93, 0xf5, 0x22, 0xdb, 0x83, 0x28, 0x44, + 0xaa, 0x11, 0x7a, 0x91, 0xad, 0x11, 0x78, 0x91, 0xed, 0xbc, 0xe9, 0xc1, 0x43, 0xc7, 0xfd, 0xf2, + 0xde, 0xfd, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x63, 0xfb, 0xcd, 0x93, 0x03, 0x05, 0x00, 0x00, } diff --git a/api/api.proto b/api/api.proto index f563f7a0..b4c46346 100644 --- a/api/api.proto +++ b/api/api.proto @@ -37,10 +37,59 @@ message DeleteClientResp { // TODO(ericchiang): expand this. +// Password is an email for password mapping managed by the storage. +message Password { + string email = 1; + + // Currently we do not accept plain text passwords. Could be an option in the future. + bytes hash = 2; + string username = 3; + string user_id = 4; +} + +// CreatePasswordReq is a request to make a password. +message CreatePasswordReq { + Password password = 1; +} + +// CreatePasswordResp returns the response from creating a password. +message CreatePasswordResp { + bool already_exists = 1; +} + +// UpdatePasswordReq is a request to modify an existing password. +message UpdatePasswordReq { + // The email used to lookup the password. This field cannot be modified + string email = 1; + bytes new_hash = 2; + string new_username = 3; +} + +// UpdatePasswordResp returns the response from modifying an existing password. +message UpdatePasswordResp { + bool not_found = 1; +} + +// DeletePasswordReq is a request to delete a password. +message DeletePasswordReq { + string email = 1; +} + +// DeletePasswordResp returns the response from deleting a password. +message DeletePasswordResp { + bool not_found = 1; +} + // Dex represents the dex gRPC service. service Dex { // CreateClient attempts to create the client. rpc CreateClient(CreateClientReq) returns (CreateClientResp) {}; // DeleteClient attempts to delete the provided client. rpc DeleteClient(DeleteClientReq) returns (DeleteClientResp) {}; + // CreatePassword attempts to create the password. + rpc CreatePassword(CreatePasswordReq) returns (CreatePasswordResp) {}; + // UpdatePassword attempts to modify existing password. + rpc UpdatePassword(UpdatePasswordReq) returns (UpdatePasswordResp) {}; + // DeletePassword attempts to delete the password. + rpc DeletePassword(DeletePasswordReq) returns (DeletePasswordResp) {}; } diff --git a/server/api.go b/server/api.go index 41fec020..9dfe5369 100644 --- a/server/api.go +++ b/server/api.go @@ -2,8 +2,10 @@ package server import ( "errors" + "fmt" "log" + "golang.org/x/crypto/bcrypt" "golang.org/x/net/context" "github.com/coreos/dex/api" @@ -43,7 +45,7 @@ func (d dexAPI) CreateClient(ctx context.Context, req *api.CreateClientReq) (*ap if err := d.s.CreateClient(c); err != nil { log.Printf("api: failed to create client: %v", err) // TODO(ericchiang): Surface "already exists" errors. - return nil, err + return nil, fmt.Errorf("create client: %v", err) } return &api.CreateClientResp{ @@ -58,7 +60,102 @@ func (d dexAPI) DeleteClient(ctx context.Context, req *api.DeleteClientReq) (*ap return &api.DeleteClientResp{NotFound: true}, nil } log.Printf("api: failed to delete client: %v", err) - return nil, err + return nil, fmt.Errorf("delete client: %v", err) } return &api.DeleteClientResp{}, nil } + +// checkCost returns an error if the hash provided does not meet minimum cost requirement +func checkCost(hash []byte) error { + actual, err := bcrypt.Cost(hash) + if err != nil { + return fmt.Errorf("parsing bcrypt hash: %v", err) + } + if actual < bcrypt.DefaultCost { + return fmt.Errorf("given hash cost = %d, does not meet minimum cost requirement = %d", actual, bcrypt.DefaultCost) + } + return nil +} + +func (d dexAPI) CreatePassword(ctx context.Context, req *api.CreatePasswordReq) (*api.CreatePasswordResp, error) { + if req.Password == nil { + return nil, errors.New("no password supplied") + } + if req.Password.UserId == "" { + return nil, errors.New("no user ID supplied") + } + if req.Password.Hash != nil { + if err := checkCost(req.Password.Hash); err != nil { + return nil, err + } + } else { + return nil, errors.New("no hash of password supplied") + } + + p := storage.Password{ + Email: req.Password.Email, + Hash: req.Password.Hash, + Username: req.Password.Username, + UserID: req.Password.UserId, + } + if err := d.s.CreatePassword(p); err != nil { + log.Printf("api: failed to create password: %v", err) + return nil, fmt.Errorf("create password: %v", err) + } + + return &api.CreatePasswordResp{}, nil +} + +func (d dexAPI) UpdatePassword(ctx context.Context, req *api.UpdatePasswordReq) (*api.UpdatePasswordResp, error) { + if req.Email == "" { + return nil, errors.New("no email supplied") + } + if req.NewHash == nil && req.NewUsername == "" { + return nil, errors.New("nothing to update") + } + + if req.NewHash != nil { + if err := checkCost(req.NewHash); err != nil { + return nil, err + } + } + + updater := func(old storage.Password) (storage.Password, error) { + if req.NewHash != nil { + old.Hash = req.NewHash + } + + if req.NewUsername != "" { + old.Username = req.NewUsername + } + + return old, nil + } + + if err := d.s.UpdatePassword(req.Email, updater); err != nil { + if err == storage.ErrNotFound { + return &api.UpdatePasswordResp{NotFound: true}, nil + } + log.Printf("api: failed to update password: %v", err) + return nil, fmt.Errorf("update password: %v", err) + } + + return &api.UpdatePasswordResp{}, nil +} + +func (d dexAPI) DeletePassword(ctx context.Context, req *api.DeletePasswordReq) (*api.DeletePasswordResp, error) { + if req.Email == "" { + return nil, errors.New("no email supplied") + } + + err := d.s.DeletePassword(req.Email) + if err != nil { + if err == storage.ErrNotFound { + return &api.DeletePasswordResp{NotFound: true}, nil + } + log.Printf("api: failed to delete password: %v", err) + return nil, fmt.Errorf("delete password: %v", err) + } + return &api.DeletePasswordResp{}, nil + +} diff --git a/server/api_test.go b/server/api_test.go index abb4e431..f6eb7a4d 100644 --- a/server/api_test.go +++ b/server/api_test.go @@ -1 +1,59 @@ package server + +import ( + "context" + "testing" + + "github.com/coreos/dex/api" + "github.com/coreos/dex/storage/memory" +) + +// Attempts to create, update and delete a test Password +func TestPassword(t *testing.T) { + s := memory.New() + serv := NewAPI(s) + + ctx := context.Background() + p := api.Password{ + Email: "test@example.com", + // bcrypt hash of the value "test1" with cost 10 + Hash: []byte("$2a$10$XVMN/Fid.Ks4CXgzo8fpR.iU1khOMsP5g9xQeXuBm1wXjRX8pjUtO"), + Username: "test", + UserId: "test123", + } + + createReq := api.CreatePasswordReq{ + Password: &p, + } + + if _, err := serv.CreatePassword(ctx, &createReq); err != nil { + t.Fatalf("Unable to create password: %v", err) + } + + updateReq := api.UpdatePasswordReq{ + Email: "test@example.com", + NewUsername: "test1", + } + + if _, err := serv.UpdatePassword(ctx, &updateReq); err != nil { + t.Fatalf("Unable to update password: %v", err) + } + + pass, err := s.GetPassword(updateReq.Email) + if err != nil { + t.Fatalf("Unable to retrieve password: %v", err) + } + + if pass.Username != updateReq.NewUsername { + t.Fatalf("UpdatePassword failed. Expected username %s retrieved %s", updateReq.NewUsername, pass.Username) + } + + deleteReq := api.DeletePasswordReq{ + Email: "test@example.com", + } + + if _, err := serv.DeletePassword(ctx, &deleteReq); err != nil { + t.Fatalf("Unable to delete password: %v", err) + } + +} diff --git a/storage/storage.go b/storage/storage.go index 032ba4ed..66ac974d 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -238,8 +238,7 @@ type Password struct { // (cough cough, kubernetes), must map this value appropriately. Email string `yaml:"email"` - // Bcrypt encoded hash of the password. This package recommends a cost value of at - // least 14. + // Bcrypt encoded hash of the password. This package enforces a min cost value of 10 Hash []byte `yaml:"hash"` // Optional username to display. NOT used during login.