Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Demo6 #2

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/gosec.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Run Gosec
on:
push:
branches:
- master
- main
pull_request:

jobs:
tests:
runs-on: ubuntu-latest
env:
GO111MODULE: on
steps:
- name: Checkout Source
uses: actions/checkout@v3
- name: Run Gosec Security Scanner
uses: securego/gosec@master
with:
args: ./...
34 changes: 34 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: golangci-lint
on:
push:
branches:
- master
- main
pull_request:

permissions:
contents: read
# Optional: allow read access to pull request. Use with `only-new-issues` option.
# pull-requests: read

jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '1.20'
cache: false
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.53
# By default we have below linters set up:
# errcheck - checking for any unchecked errors in our code
# gosimple - checking if the code can be simplified
# govet - reports suspicious constructs
# ineffassign - detects when assignments to variables are not used
# staticcheck - swiss-army knife, checks for bugs, performance issues and more
# unused - checks for unused constants, variables, functions and types
48 changes: 48 additions & 0 deletions demo5/data/note_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package data

import (
"github.com/addetz/secure-code-go/demo4/db"
"github.com/google/uuid"
)

type SecretNote struct {
ID string `json:"id"`
Username string `json:"username"`
Text string `json:"text"`
}

// SecretNoteService maintains the user notes.
type SecretNoteService struct {
dbService db.DatabaseService
}

// NewSecretNoteService creates a SecretNoteService that is ready to use.
func NewSecretNoteService(dbService db.DatabaseService) *SecretNoteService {
return &SecretNoteService{
dbService: dbService,
}
}

// Add adds a new SecretNote for the given user by using the SecretNoteService.
func (ns *SecretNoteService) Add(user string, n SecretNote) error {
id := uuid.New().String()
return ns.dbService.AddNote(id, user, n.Text)
}

// Get returns all the SecretNotes of a given user by using the SecretNoteService.
func (ns *SecretNoteService) GetAll(user string) ([]SecretNote, error) {
dbNotes, err := ns.dbService.GetUserNotes(user)
if err != nil {
return nil, err
}
var notes []SecretNote
for _, n := range dbNotes {
notes = append(notes, SecretNote{
ID: n.ID,
Username: n.Username,
Text: n.Text,
})
}

return notes, nil
}
66 changes: 66 additions & 0 deletions demo5/data/note_service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package data_test

import (
"errors"
"testing"

"github.com/addetz/secure-code-go/demo4/data"
"github.com/addetz/secure-code-go/demo4/db"
"github.com/addetz/secure-code-go/demo4/mocks"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func TestAddNote(t *testing.T) {
mockDB := new(mocks.DatabaseServiceMock)
notes := data.NewSecretNoteService(mockDB)
user := "user1"
note := data.SecretNote{
Text: "My Secret Note",
}
mockDB.On("AddNote", mock.AnythingOfType("string"), user, note.Text).Return(nil)
err := notes.Add(user, note)
assert.Nil(t, err)
}

func TestGetAllNotes(t *testing.T) {
t.Run("no notes found", func(t *testing.T) {
user := "user1"
mockDB := new(mocks.DatabaseServiceMock)
noteService := data.NewSecretNoteService(mockDB)
mockDB.On("GetUserNotes", user).Return(nil, errors.New("no notes found"))
notes, err := noteService.GetAll(user)
assert.Nil(t, notes)
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "no notes found")
})
t.Run("notes found", func(t *testing.T) {
user := "user1"
dbNotes := []db.Note{
{
ID: uuid.New().String(),
Username: user,
Text: "My first note",
},
{
ID: uuid.New().String(),
Username: user,
Text: "My second note",
},
}
mockDB := new(mocks.DatabaseServiceMock)
noteService := data.NewSecretNoteService(mockDB)
mockDB.On("GetUserNotes", user).Return(dbNotes, nil)
notes, err := noteService.GetAll(user)
assert.Nil(t, err)
assert.NotNil(t, notes)
assert.Equal(t, len(dbNotes), len(notes))
for i, n := range dbNotes {
assert.Equal(t, n.ID, notes[i].ID)
assert.Equal(t, n.Username, notes[i].Username)
assert.Equal(t, n.Text, notes[i].Text)

}
})
}
62 changes: 62 additions & 0 deletions demo5/data/user_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package data

import (
"github.com/addetz/secure-code-go/demo4/db"
"github.com/pkg/errors"

passwordvalidator "github.com/wagslane/go-password-validator"
"golang.org/x/crypto/bcrypt"
)

const minEntropyBits = 60

type User struct {
Username, Password string
}

// UserService holds
type UserService struct {
dbService db.DatabaseService
}

// NewUserService creates a ready to use user service.
func NewUserService(dbService db.DatabaseService) *UserService {
return &UserService{
dbService: dbService,
}
}

// Add validates a user password and creates a new user.
func (us *UserService) Add(name, password string) error {
if _, err := us.dbService.GetUser(name); err == nil {
return errors.New("user exists already, please log in instead")
}

err := passwordvalidator.Validate(password, minEntropyBits)
if err != nil {
return errors.Wrap(err, "validate new user password")
}

hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return err
}
return us.dbService.AddUser(name, string(hashedPassword))
}

// ValidatePassword checks the provided password of an existing user.
func (us *UserService) ValidatePassword(name, providedPwd string) error {
user, err := us.dbService.GetUser(name)
if err != nil {
return errors.Wrap(err, "user does not exist")
}
return bcrypt.CompareHashAndPassword([]byte(user.Pwd), []byte(providedPwd))
}

// ValidateUser checks the provided username belongs to an existing user.
func (us *UserService) ValidateUser(name string) error {
if _, err := us.dbService.GetUser(name); err != nil {
return errors.New("user not found")
}
return nil
}
84 changes: 84 additions & 0 deletions demo5/data/user_service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package data_test

import (
"errors"
"testing"

"github.com/addetz/secure-code-go/demo4/data"
"github.com/addetz/secure-code-go/demo4/db"
"github.com/addetz/secure-code-go/demo4/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"golang.org/x/crypto/bcrypt"
)

func TestAdd(t *testing.T) {
t.Run("insufficient password", func(t *testing.T) {
name := "user1"
mockDB := new(mocks.DatabaseServiceMock)
us := data.NewUserService(mockDB)
mockDB.On("GetUser", name).Return(nil, errors.New("no user exists"))
err := us.Add(name, "test")
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "validate new user password: insecure password")
})
t.Run("successful add", func(t *testing.T) {
name := "user1"
password := "test-horse-pen-clam"
mockDB := new(mocks.DatabaseServiceMock)
us := data.NewUserService(mockDB)
mockDB.On("GetUser", name).Return(nil, errors.New("no user exists"))
mockDB.On("AddUser", name, mock.AnythingOfType("string")).Return(nil)
err := us.Add(name, password)
assert.Nil(t, err)
})
t.Run("duplicate user", func(t *testing.T) {
name := "user1"
password := "test-horse-pen-clam"
mockDB := new(mocks.DatabaseServiceMock)
us := data.NewUserService(mockDB)
mockDB.On("GetUser", name).Return(nil, nil)
err := us.Add(name, password)
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "user exists already")
})
}

func TestValidate(t *testing.T) {
t.Run("successful validate", func(t *testing.T) {
name := "user1"
password := "test-horse-pen-clam"
expected, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
mockDB := new(mocks.DatabaseServiceMock)
us := data.NewUserService(mockDB)
mockDB.On("GetUser", name).Return(&db.User{
Username: name,
Pwd: string(expected),
}, nil)
err := us.ValidatePassword(name, password)
assert.Nil(t, err)
})
t.Run("failed validate", func(t *testing.T) {
name := "user1"
password := "test-horse-pen-clam"
expected, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
mockDB := new(mocks.DatabaseServiceMock)
mockDB.On("GetUser", name).Return(&db.User{
Username: name,
Pwd: string(expected),
}, nil)
us := data.NewUserService(mockDB)
err := us.ValidatePassword(name, "garbage-password")
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "hashedPassword is not the hash of the given password")
})
t.Run("inexistent user", func(t *testing.T) {
name := "user1"
mockDB := new(mocks.DatabaseServiceMock)
us := data.NewUserService(mockDB)
mockDB.On("GetUser", name).Return(nil, errors.New("no user exists"))
err := us.ValidatePassword(name, "garbage-password")
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "user does not exist")
})
}
Loading