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"`
Role string `json:"role"`
PasswordChangeRequired bool `json:"password_change_required"`
AccountLocked bool `json:"account_locked"`
}
func (ur *userRequest) Validate(existingUser *models.User) error {
@ -107,6 +108,7 @@ func (as *Server) Users(w http.ResponseWriter, r *http.Request) {
ApiKey: auth.GenerateSecureKey(auth.APIKeyLength),
Role: role,
RoleID: role.ID,
PasswordChangeRequired: ur.PasswordChangeRequired,
}
err = models.PutUser(&user)
if err != nil {

View File

@ -49,6 +49,14 @@ func setupTest(t *testing.T) *testContext {
if err != nil {
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
// Start the phishing server
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)
}
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)
Flash(w, r, "danger", "Invalid Username/Password")
Flash(w, r, "danger", message)
params := struct {
User models.User
Title string
@ -376,14 +376,18 @@ func (as *AdminServer) Login(w http.ResponseWriter, r *http.Request) {
u, err := models.GetUserByUsername(username)
if err != nil {
log.Error(err)
as.handleInvalidLogin(w, r)
as.handleInvalidLogin(w, r, "Invalid Username/Password")
return
}
// Validate the user's password
err = auth.ValidatePassword(password, u.Hash)
if err != nil {
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
}
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)
}
}
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"`
RoleID int64 `json:"-"`
PasswordChangeRequired bool `json:"password_change_required"`
AccountLocked bool `json:"account_locked"`
LastLogin time.Time `json:"last_login"`
}

View File

@ -11,7 +11,8 @@ const save = (id) => {
username: $("#username").val(),
password: $("#password").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
if (id != -1) {
@ -49,6 +50,8 @@ const dismiss = () => {
$("#password").val("")
$("#confirm_password").val("")
$("#role").val("")
$("#force_password_change_checkbox").prop('checked', true)
$("#account_locked_checkbox").prop('checked', false)
$("#modal\\.flashes").empty()
}
@ -66,7 +69,8 @@ const edit = (id) => {
$("#username").val(user.username)
$("#role").val(user.role.slug)
$("#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 () {
errorFlash("Error fetching user")

View File

@ -67,6 +67,10 @@
<input id="force_password_change_checkbox" type="checkbox" checked>
<label for="force_password_change_checkbox">Require the user to set a new password</label>
</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>
<div class="form-group" id="role-select">
<select class="form-control" placeholder="" id="role" />