Added functionality to lock accounts (+bug fix) (#2060)

* Added functionality to lock accounts

* Fixed typo and added test case for locked account
pull/2067/head
Glenn Wilkinson 2020-12-07 15:56:05 +01:00 committed by GitHub
parent 8b8e88b077
commit ced5261678
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 58 additions and 11 deletions

View File

@ -33,6 +33,7 @@ type userRequest struct {
Password string `json:"password"` Password string `json:"password"`
Role string `json:"role"` Role string `json:"role"`
PasswordChangeRequired bool `json:"password_change_required"` PasswordChangeRequired bool `json:"password_change_required"`
AccountLocked bool `json:"account_locked"`
} }
func (ur *userRequest) Validate(existingUser *models.User) error { func (ur *userRequest) Validate(existingUser *models.User) error {
@ -102,11 +103,12 @@ func (as *Server) Users(w http.ResponseWriter, r *http.Request) {
return return
} }
user := models.User{ user := models.User{
Username: ur.Username, Username: ur.Username,
Hash: hash, Hash: hash,
ApiKey: auth.GenerateSecureKey(auth.APIKeyLength), ApiKey: auth.GenerateSecureKey(auth.APIKeyLength),
Role: role, Role: role,
RoleID: role.ID, RoleID: role.ID,
PasswordChangeRequired: ur.PasswordChangeRequired,
} }
err = models.PutUser(&user) err = models.PutUser(&user)
if err != nil { if err != nil {

View File

@ -49,6 +49,14 @@ func setupTest(t *testing.T) *testContext {
if err != nil { if err != nil {
t.Fatalf("error getting first user from database: %v", err) t.Fatalf("error getting first user from database: %v", err)
} }
// Create a second user to test account locked status
u2 := models.User{Username: "houdini", Hash: hash, AccountLocked: true}
models.PutUser(&u2)
if err != nil {
t.Fatalf("error creating new user: %v", err)
}
ctx.apiKey = u.ApiKey ctx.apiKey = u.ApiKey
// Start the phishing server // Start the phishing server
ctx.phishServer = httptest.NewUnstartedServer(NewPhishingServer(ctx.config.PhishConf).server.Handler) ctx.phishServer = httptest.NewUnstartedServer(NewPhishingServer(ctx.config.PhishConf).server.Handler)

View File

@ -304,9 +304,9 @@ func (as *AdminServer) nextOrIndex(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, next, 302) http.Redirect(w, r, next, 302)
} }
func (as *AdminServer) handleInvalidLogin(w http.ResponseWriter, r *http.Request) { func (as *AdminServer) handleInvalidLogin(w http.ResponseWriter, r *http.Request, message string) {
session := ctx.Get(r, "session").(*sessions.Session) session := ctx.Get(r, "session").(*sessions.Session)
Flash(w, r, "danger", "Invalid Username/Password") Flash(w, r, "danger", message)
params := struct { params := struct {
User models.User User models.User
Title string Title string
@ -376,14 +376,18 @@ func (as *AdminServer) Login(w http.ResponseWriter, r *http.Request) {
u, err := models.GetUserByUsername(username) u, err := models.GetUserByUsername(username)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
as.handleInvalidLogin(w, r) as.handleInvalidLogin(w, r, "Invalid Username/Password")
return return
} }
// Validate the user's password // Validate the user's password
err = auth.ValidatePassword(password, u.Hash) err = auth.ValidatePassword(password, u.Hash)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
as.handleInvalidLogin(w, r) as.handleInvalidLogin(w, r, "Invalid Username/Password")
return
}
if u.AccountLocked == true {
as.handleInvalidLogin(w, r, "Account Locked")
return return
} }
u.LastLogin = time.Now().UTC() u.LastLogin = time.Now().UTC()

View File

@ -117,3 +117,14 @@ func TestSuccessfulRedirect(t *testing.T) {
t.Fatalf("unexpected Location header received. expected %s got %s", next, url.Path) t.Fatalf("unexpected Location header received. expected %s got %s", next, url.Path)
} }
} }
func TestAccountLocked(t *testing.T) {
ctx := setupTest(t)
defer tearDown(t, ctx)
resp := attemptLogin(t, ctx, nil, "houdini", "gophish", "")
got := resp.StatusCode
expected := http.StatusUnauthorized
if got != expected {
t.Fatalf("invalid status code received. expected %d got %d", expected, got)
}
}

View File

@ -0,0 +1,7 @@
-- +goose Up
-- SQL in section 'Up' is executed when this migration is applied
ALTER TABLE `users` ADD COLUMN account_locked BOOLEAN;
-- +goose Down
-- SQL section 'Down' is executed when this migration is rolled back

View File

@ -0,0 +1,6 @@
-- +goose Up
-- SQL in section 'Up' is executed when this migration is applied
ALTER TABLE users ADD COLUMN account_locked BOOLEAN;
-- +goose Down
-- SQL section 'Down' is executed when this migration is rolled back

View File

@ -21,6 +21,7 @@ type User struct {
Role Role `json:"role" gorm:"association_autoupdate:false;association_autocreate:false"` Role Role `json:"role" gorm:"association_autoupdate:false;association_autocreate:false"`
RoleID int64 `json:"-"` RoleID int64 `json:"-"`
PasswordChangeRequired bool `json:"password_change_required"` PasswordChangeRequired bool `json:"password_change_required"`
AccountLocked bool `json:"account_locked"`
LastLogin time.Time `json:"last_login"` LastLogin time.Time `json:"last_login"`
} }

View File

@ -11,7 +11,8 @@ const save = (id) => {
username: $("#username").val(), username: $("#username").val(),
password: $("#password").val(), password: $("#password").val(),
role: $("#role").val(), role: $("#role").val(),
password_change_required: $("#force_password_change_checkbox").prop('checked') password_change_required: $("#force_password_change_checkbox").prop('checked'),
account_locked: $("#account_locked_checkbox").prop('checked')
} }
// Submit the user // Submit the user
if (id != -1) { if (id != -1) {
@ -49,6 +50,8 @@ const dismiss = () => {
$("#password").val("") $("#password").val("")
$("#confirm_password").val("") $("#confirm_password").val("")
$("#role").val("") $("#role").val("")
$("#force_password_change_checkbox").prop('checked', true)
$("#account_locked_checkbox").prop('checked', false)
$("#modal\\.flashes").empty() $("#modal\\.flashes").empty()
} }
@ -66,7 +69,8 @@ const edit = (id) => {
$("#username").val(user.username) $("#username").val(user.username)
$("#role").val(user.role.slug) $("#role").val(user.role.slug)
$("#role").trigger("change") $("#role").trigger("change")
$("#force_password_change_checkbox").prop('checked', false) $("#force_password_change_checkbox").prop('checked', user.password_change_required)
$("#account_locked_checkbox").prop('checked', user.account_locked)
}) })
.error(function () { .error(function () {
errorFlash("Error fetching user") errorFlash("Error fetching user")

View File

@ -67,6 +67,10 @@
<input id="force_password_change_checkbox" type="checkbox" checked> <input id="force_password_change_checkbox" type="checkbox" checked>
<label for="force_password_change_checkbox">Require the user to set a new password</label> <label for="force_password_change_checkbox">Require the user to set a new password</label>
</div> </div>
<div class="checkbox checkbox-primary">
<input id="account_locked_checkbox" type="checkbox">
<label for="account_locked_checkbox">Account Locked</label>
</div>
<label class="control-label" for="role">Role:</label> <label class="control-label" for="role">Role:</label>
<div class="form-group" id="role-select"> <div class="form-group" id="role-select">
<select class="form-control" placeholder="" id="role" /> <select class="form-control" placeholder="" id="role" />