mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-25 17:10:28 +00:00
web UI: improve user cloning
This commit is contained in:
parent
bc397002d4
commit
e536a638c9
6 changed files with 96 additions and 11 deletions
|
@ -261,6 +261,41 @@ func (u *User) HideConfidentialData() {
|
|||
}
|
||||
}
|
||||
|
||||
// DecryptSecrets tries to decrypts kms secrets
|
||||
func (u *User) DecryptSecrets() error {
|
||||
switch u.FsConfig.Provider {
|
||||
case S3FilesystemProvider:
|
||||
if u.FsConfig.S3Config.AccessSecret.IsEncrypted() {
|
||||
return u.FsConfig.S3Config.AccessSecret.Decrypt()
|
||||
}
|
||||
case GCSFilesystemProvider:
|
||||
if u.FsConfig.GCSConfig.Credentials.IsEncrypted() {
|
||||
return u.FsConfig.GCSConfig.Credentials.Decrypt()
|
||||
}
|
||||
case AzureBlobFilesystemProvider:
|
||||
if u.FsConfig.AzBlobConfig.AccountKey.IsEncrypted() {
|
||||
return u.FsConfig.AzBlobConfig.AccountKey.Decrypt()
|
||||
}
|
||||
case CryptedFilesystemProvider:
|
||||
if u.FsConfig.CryptConfig.Passphrase.IsEncrypted() {
|
||||
return u.FsConfig.CryptConfig.Passphrase.Decrypt()
|
||||
}
|
||||
case SFTPFilesystemProvider:
|
||||
if u.FsConfig.SFTPConfig.Password.IsEncrypted() {
|
||||
if err := u.FsConfig.SFTPConfig.Password.Decrypt(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if u.FsConfig.SFTPConfig.PrivateKey.IsEncrypted() {
|
||||
if err := u.FsConfig.SFTPConfig.PrivateKey.Decrypt(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPermissionsForPath returns the permissions for the given path.
|
||||
// The path must be an SFTP path
|
||||
func (u *User) GetPermissionsForPath(p string) []string {
|
||||
|
|
|
@ -235,6 +235,7 @@ func TestMain(m *testing.M) {
|
|||
waitTCPListening(ftpdConf.Bindings[0].GetAddress())
|
||||
|
||||
// ensure all the initial connections to check if the service is alive are disconnected
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
for len(common.Connections.GetStats()) > 0 {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
|
|
@ -2079,6 +2079,10 @@ func TestProviderErrors(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = os.Remove(backupFilePath)
|
||||
assert.NoError(t, err)
|
||||
req, err := http.NewRequest(http.MethodGet, webUserPath+"?cloneFromId=1234", nil)
|
||||
assert.NoError(t, err)
|
||||
rr := executeRequest(req)
|
||||
checkResponseCode(t, http.StatusInternalServerError, rr.Code)
|
||||
err = config.LoadConfig(configDir, "")
|
||||
assert.NoError(t, err)
|
||||
providerConf := config.GetProviderConf()
|
||||
|
@ -3320,6 +3324,47 @@ func TestWebUserUpdateMock(t *testing.T) {
|
|||
checkResponseCode(t, http.StatusOK, rr.Code)
|
||||
}
|
||||
|
||||
func TestRenderWebCloneUserMock(t *testing.T) {
|
||||
user, _, err := httpd.AddUser(getTestUser(), http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, webUserPath+"?cloneFromId=a", nil)
|
||||
assert.NoError(t, err)
|
||||
rr := executeRequest(req)
|
||||
checkResponseCode(t, http.StatusBadRequest, rr.Code)
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, webUserPath+"?cloneFromId=1234", nil)
|
||||
assert.NoError(t, err)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr.Code)
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, webUserPath+fmt.Sprintf("?cloneFromId=%v", user.ID), nil)
|
||||
assert.NoError(t, err)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr.Code)
|
||||
|
||||
user.FsConfig = dataprovider.Filesystem{
|
||||
Provider: dataprovider.CryptedFilesystemProvider,
|
||||
CryptConfig: vfs.CryptFsConfig{
|
||||
Passphrase: kms.NewPlainSecret("secret"),
|
||||
},
|
||||
}
|
||||
err = user.FsConfig.CryptConfig.Passphrase.Encrypt()
|
||||
assert.NoError(t, err)
|
||||
user.FsConfig.CryptConfig.Passphrase.SetStatus(kms.SecretStatusAWS)
|
||||
user.Password = defaultPassword
|
||||
err = dataprovider.UpdateUser(user)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, webUserPath+fmt.Sprintf("?cloneFromId=%v", user.ID), nil)
|
||||
assert.NoError(t, err)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusInternalServerError, rr.Code)
|
||||
|
||||
_, err = httpd.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestWebUserS3Mock(t *testing.T) {
|
||||
user := getTestUser()
|
||||
userAsJSON := getUserAsJSON(t, user)
|
||||
|
|
|
@ -688,6 +688,10 @@ func handleWebAddUserGet(w http.ResponseWriter, r *http.Request) {
|
|||
if err == nil {
|
||||
user.ID = 0
|
||||
user.Username = ""
|
||||
if err := user.DecryptSecrets(); err != nil {
|
||||
renderInternalServerErrorPage(w, err)
|
||||
return
|
||||
}
|
||||
renderAddUserPage(w, user, "")
|
||||
} else if _, ok := err.(*dataprovider.RecordNotFoundError); ok {
|
||||
renderNotFoundPage(w, err)
|
||||
|
|
|
@ -338,7 +338,7 @@
|
|||
<div class="col-sm-2"></div>
|
||||
<label for="idS3AccessSecret" class="col-sm-2 col-form-label">Access Secret</label>
|
||||
<div class="col-sm-3">
|
||||
<input type="text" class="form-control" id="idS3AccessSecret" name="s3_access_secret" placeholder=""
|
||||
<input type="password" class="form-control" id="idS3AccessSecret" name="s3_access_secret" placeholder=""
|
||||
value="{{if .User.FsConfig.S3Config.AccessSecret.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.User.FsConfig.S3Config.AccessSecret.GetPayload}}{{end}}" maxlength="1000">
|
||||
</div>
|
||||
</div>
|
||||
|
@ -449,7 +449,7 @@
|
|||
<div class="form-group row azblob">
|
||||
<label for="idAzAccountKey" class="col-sm-2 col-form-label">Account Key</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="idAzAccountKey" name="az_account_key" placeholder=""
|
||||
<input type="password" class="form-control" id="idAzAccountKey" name="az_account_key" placeholder=""
|
||||
value="{{if .User.FsConfig.AzBlobConfig.AccountKey.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.User.FsConfig.AzBlobConfig.AccountKey.GetPayload}}{{end}}" maxlength="1000">
|
||||
</div>
|
||||
</div>
|
||||
|
@ -522,7 +522,7 @@
|
|||
<div class="form-group row crypt">
|
||||
<label for="idCryptPassphrase" class="col-sm-2 col-form-label">Passphrase</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="idCryptPassphrase" name="crypt_passphrase" placeholder=""
|
||||
<input type="password" class="form-control" id="idCryptPassphrase" name="crypt_passphrase" placeholder=""
|
||||
value="{{if .User.FsConfig.CryptConfig.Passphrase.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.User.FsConfig.CryptConfig.Passphrase.GetPayload}}{{end}}" maxlength="1000">
|
||||
</div>
|
||||
</div>
|
||||
|
@ -544,7 +544,7 @@
|
|||
<div class="form-group row sftp">
|
||||
<label for="idSFTPPassword" class="col-sm-2 col-form-label">Password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="idSFTPPassword" name="sftp_password" placeholder=""
|
||||
<input type="password" class="form-control" id="idSFTPPassword" name="sftp_password" placeholder=""
|
||||
value="{{if .User.FsConfig.SFTPConfig.Password.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.User.FsConfig.SFTPConfig.Password.GetPayload}}{{end}}" maxlength="1000">
|
||||
</div>
|
||||
</div>
|
||||
|
@ -552,7 +552,7 @@
|
|||
<div class="form-group row sftp">
|
||||
<label for="idSFTPPrivateKey" class="col-sm-2 col-form-label">Private key</label>
|
||||
<div class="col-sm-10">
|
||||
<textarea type="text" class="form-control" id="idSFTPPrivateKey" name="sftp_private_key"
|
||||
<textarea type="password" class="form-control" id="idSFTPPrivateKey" name="sftp_private_key"
|
||||
rows="3">{{if .User.FsConfig.SFTPConfig.PrivateKey.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.User.FsConfig.SFTPConfig.PrivateKey.GetPayload}}{{end}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -97,7 +97,7 @@
|
|||
|
||||
function deleteAction() {
|
||||
var table = $('#dataTable').DataTable();
|
||||
table.button(2).enable(false);
|
||||
table.button(3).enable(false);
|
||||
var userID = table.row({ selected: true }).data()[0];
|
||||
var path = '{{.APIUserURL}}' + "/" + userID;
|
||||
$('#deleteModal').modal('hide');
|
||||
|
@ -107,12 +107,12 @@
|
|||
dataType: 'json',
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
table.button(2).enable(true);
|
||||
table.button(3).enable(true);
|
||||
window.location.href = '{{.UsersURL}}';
|
||||
},
|
||||
error: function ($xhr, textStatus, errorThrown) {
|
||||
console.log("delete error")
|
||||
table.button(2).enable(true);
|
||||
table.button(3).enable(true);
|
||||
var txt = "Unable to delete the selected user";
|
||||
if ($xhr) {
|
||||
var json = $xhr.responseJSON;
|
||||
|
@ -173,7 +173,7 @@
|
|||
$.fn.dataTable.ext.buttons.quota_scan = {
|
||||
text: 'Quota scan',
|
||||
action: function (e, dt, node, config) {
|
||||
table.button(3).enable(false);
|
||||
table.button(4).enable(false);
|
||||
var username = dt.row({ selected: true }).data()[1];
|
||||
var path = '{{.APIQuotaScanURL}}'
|
||||
$.ajax({
|
||||
|
@ -183,7 +183,7 @@
|
|||
data: JSON.stringify({ "username": username }),
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
table.button(3).enable(true);
|
||||
table.button(4).enable(true);
|
||||
$('#successTxt').text("Quota scan started for the selected user. Please reload the user's page to check when the scan ends");
|
||||
$('#successMsg').show();
|
||||
setTimeout(function () {
|
||||
|
@ -192,7 +192,7 @@
|
|||
},
|
||||
error: function ($xhr, textStatus, errorThrown) {
|
||||
console.log("quota scan error")
|
||||
table.button(3).enable(true);
|
||||
table.button(4).enable(true);
|
||||
var txt = "Unable to update quota for the selected user";
|
||||
if ($xhr) {
|
||||
var json = $xhr.responseJSON;
|
||||
|
|
Loading…
Reference in a new issue