Skip to content
Snippets Groups Projects
user.go 6.24 KiB
Newer Older
  • Learn to ignore specific revisions
  • Fabian Seidl's avatar
    Fabian Seidl committed
    	"encoding/base64"
    
    	"time"
    
    	apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac"
    	"code.fbi.h-da.de/danet/gosdn/controller/metrics"
    	"code.fbi.h-da.de/danet/gosdn/controller/rbac"
    	"code.fbi.h-da.de/danet/gosdn/controller/store"
    	"github.com/google/uuid"
    	"github.com/prometheus/client_golang/prometheus"
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    	"github.com/sethvargo/go-password/password"
    
    	log "github.com/sirupsen/logrus"
    	"google.golang.org/grpc/codes"
    	"google.golang.org/grpc/status"
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    
    	"golang.org/x/crypto/argon2"
    
    )
    
    // User holds a JWTManager and represents a UserServiceServer.
    type User struct {
    	apb.UnimplementedUserServiceServer
    	jwtManager *rbac.JWTManager
    }
    
    // NewUserServer receives a JWTManager and returns a new UserServer.
    func NewUserServer(jwtManager *rbac.JWTManager) *User {
    	return &User{
    		jwtManager: jwtManager,
    	}
    }
    
    // CreateUsers creates new users, can be 1 or more
    func (u User) CreateUsers(ctx context.Context, request *apb.CreateUsersRequest) (*apb.CreateUsersResponse, error) {
    	labels := prometheus.Labels{"service": "auth", "rpc": "post"}
    	start := metrics.StartHook(labels, grpcRequestsTotal)
    	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
    
    	for _, u := range request.User {
    		roles := map[string]string{}
    		for key, elem := range u.Roles {
    			_, err := uuid.Parse(key)
    			if err != nil {
    				return nil, handleRPCError(labels, err)
    			}
    			roles[key] = elem
    		}
    
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    		// Generate a salt that is 16 characters long with 3 digits, 0 symbols,
    		// allowing upper and lower case letters, disallowing repeat characters.
    		salt, err := password.Generate(16, 3, 0, true, false)
    		if err != nil {
    			log.Error(err)
    			return nil, status.Errorf(codes.Aborted, "%v", err)
    		}
    
    		hashedPassword := base64.RawStdEncoding.EncodeToString(argon2.IDKey([]byte(u.Password), []byte(salt), 1, 64*1024, 4, 32))
    
    		user := rbac.NewUser(uuid.New(), u.Name, roles, string(hashedPassword), u.Token, salt)
    		err = userService.Add(user)
    
    		if err != nil {
    			log.Error(err)
    			return nil, status.Errorf(codes.Aborted, "%v", err)
    		}
    	}
    
    	return &apb.CreateUsersResponse{
    		Timestamp: time.Now().UnixNano(),
    		Status:    apb.Status_STATUS_OK,
    	}, nil
    }
    
    // GetUser returns one user by name.
    func (u User) GetUser(ctx context.Context, request *apb.GetUserRequest) (*apb.GetUserResponse, error) {
    	labels := prometheus.Labels{"service": "auth", "rpc": "get"}
    	start := metrics.StartHook(labels, grpcRequestsTotal)
    	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
    
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    	userData, err := userService.Get(store.Query{Name: request.Name})
    
    	if err != nil {
    		return nil, err
    	}
    
    	user := &apb.User{
    		Id:    userData.ID().String(),
    		Name:  userData.Name(),
    		Roles: userData.GetRoles(),
    	}
    
    	return &apb.GetUserResponse{
    		Timestamp: time.Now().UnixNano(),
    		Status:    apb.Status_STATUS_OK,
    		User:      user,
    	}, nil
    }
    
    // GetUsers returns all availbale users
    func (u User) GetUsers(ctx context.Context, request *apb.GetUsersRequest) (*apb.GetUsersResponse, error) {
    	labels := prometheus.Labels{"service": "auth", "rpc": "get"}
    	start := metrics.StartHook(labels, grpcRequestsTotal)
    	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
    
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    	userList, err := userService.GetAll()
    
    	if err != nil {
    		return nil, err
    	}
    
    	users := []*apb.User{}
    	for _, u := range userList {
    		users = append(users, &apb.User{
    			Id:    u.ID().String(),
    			Name:  u.Name(),
    			Roles: u.GetRoles(),
    		})
    	}
    
    	return &apb.GetUsersResponse{
    		Timestamp: time.Now().UnixNano(),
    		Status:    apb.Status_STATUS_OK,
    		User:      users,
    	}, nil
    }
    
    // UpdateUsers updates the user data of one or more users provided in the request
    func (u User) UpdateUsers(ctx context.Context, request *apb.UpdateUsersRequest) (*apb.UpdateUsersResponse, error) {
    	labels := prometheus.Labels{"service": "auth", "rpc": "post"}
    	start := metrics.StartHook(labels, grpcRequestsTotal)
    	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
    
    	for _, u := range request.User {
    		uid, err := uuid.Parse(u.Id)
    		if err != nil {
    			return nil, handleRPCError(labels, err)
    		}
    
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    		storedUser, err := userService.Get(store.Query{ID: uid})
    
    		if err != nil {
    			return nil, status.Errorf(codes.Canceled, "user not found %v", err)
    		}
    
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    		hashedPassword := base64.RawStdEncoding.EncodeToString(argon2.IDKey([]byte(u.Password), []byte(storedUser.GetSalt()), 1, 64*1024, 4, 32))
    
    		userToUpdate := rbac.NewUser(uid, u.Name, u.Roles, string(hashedPassword), u.Token, storedUser.GetSalt())
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    		err = userService.Update(userToUpdate)
    
    		if err != nil {
    			return nil, status.Errorf(codes.Aborted, "could not update user %v", err)
    		}
    	}
    
    	return &apb.UpdateUsersResponse{
    		Timestamp: time.Now().UnixNano(),
    		Status:    apb.Status_STATUS_OK,
    	}, nil
    }
    
    // DeleteUsers deletes one or more users provided in the request
    func (u User) DeleteUsers(ctx context.Context, request *apb.DeleteUsersRequest) (*apb.DeleteUsersResponse, error) {
    	labels := prometheus.Labels{"service": "auth", "rpc": "delete"}
    	start := metrics.StartHook(labels, grpcRequestsTotal)
    	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
    
    	for _, u := range request.Username {
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    		userToDelete, err := userService.Get(store.Query{Name: u})
    
    		if err != nil {
    			return nil, status.Errorf(codes.Canceled, "user not found %v", err)
    		}
    
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    		err = userService.Delete(userToDelete)
    
    		if err != nil {
    			return nil, status.Errorf(codes.Aborted, "error deleting user %v", err)
    		}
    	}
    	return &apb.DeleteUsersResponse{
    		Timestamp: time.Now().UnixNano(),
    		Status:    apb.Status_STATUS_OK,
    	}, nil
    }
    
    func (u User) isValidUser(user rbac.User) (bool, error) {
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    	storedUser, err := userService.Get(store.Query{Name: user.Name()})
    
    	if err != nil {
    		return false, err
    	} else if storedUser == nil {
    		return false, status.Errorf(codes.Aborted, "no user object")
    	}
    
    	if storedUser.Name() == user.Name() {
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    		salt := storedUser.GetSalt()
    
    		hashedPasswordFromLogin := base64.RawStdEncoding.EncodeToString(argon2.IDKey([]byte(user.GetPassword()), []byte(salt), 1, 64*1024, 4, 32))
    
    		if storedUser.GetPassword() == hashedPasswordFromLogin {
    
    			return true, nil
    		}
    	}
    
    	return false, status.Errorf(codes.Unauthenticated, "incorrect user name or password")
    }