2024-06-06 03:55:47 -04:00
/ * *
* SPDX - FileCopyrightText : 2016 - 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX - FileCopyrightText : 2012 - 2016 ownCloud , Inc .
* SPDX - License - Identifier : AGPL - 3.0 - or - later
2014-10-31 06:41:07 -04:00
* /
2015-03-16 09:07:53 -04:00
2024-11-27 11:01:25 -05:00
import { addPasswordConfirmationInterceptors , PwdConfirmationMode } from '@nextcloud/password-confirmation'
2025-03-07 08:22:25 -05:00
import { generateUrl } from '@nextcloud/router'
import { showError } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'
import axios , { isAxiosError } from '@nextcloud/axios'
2024-11-27 11:01:25 -05:00
2024-11-27 05:20:23 -05:00
import jQuery from 'jquery'
2024-11-27 11:01:25 -05:00
addPasswordConfirmationInterceptors ( axios )
2013-10-10 05:10:32 -04:00
2014-10-31 06:41:07 -04:00
/ * *
* Returns the selection of applicable users in the given configuration row
*
* @ param $row configuration row
* @ return array array of user names
* /
2014-08-21 09:11:21 -04:00
function getSelection ( $row ) {
2024-11-27 05:20:23 -05:00
let values = $row . find ( '.applicableUsers' ) . select2 ( 'val' )
2014-08-21 09:11:21 -04:00
if ( ! values || values . length === 0 ) {
2024-11-27 05:20:23 -05:00
values = [ ]
2014-08-21 09:11:21 -04:00
}
2024-11-27 05:20:23 -05:00
return values
2014-08-21 09:11:21 -04:00
}
2024-11-27 05:20:23 -05:00
/ * *
*
* @ param $row
* /
2023-01-11 06:30:59 -05:00
function getSelectedApplicable ( $row ) {
2024-11-27 05:20:23 -05:00
const users = [ ]
const groups = [ ]
const multiselect = getSelection ( $row )
2023-01-11 06:30:59 -05:00
$ . each ( multiselect , function ( index , value ) {
// FIXME: don't rely on string parts to detect groups...
2024-11-27 05:20:23 -05:00
const pos = ( value . indexOf ) ? value . indexOf ( '(group)' ) : - 1
2023-01-11 06:30:59 -05:00
if ( pos !== - 1 ) {
2024-11-27 05:20:23 -05:00
groups . push ( value . substr ( 0 , pos ) )
2023-01-11 06:30:59 -05:00
} else {
2024-11-27 05:20:23 -05:00
users . push ( value )
2023-01-11 06:30:59 -05:00
}
2024-11-27 05:20:23 -05:00
} )
2023-01-11 06:30:59 -05:00
// FIXME: this should be done in the multiselect change event instead
$row . find ( '.applicable' )
. data ( 'applicable-groups' , groups )
2024-11-27 05:20:23 -05:00
. data ( 'applicable-users' , users )
2023-01-11 06:30:59 -05:00
2024-11-27 05:20:23 -05:00
return { users , groups }
2023-01-11 06:30:59 -05:00
}
2024-11-27 05:20:23 -05:00
/ * *
*
* @ param $element
* @ param highlight
* /
2014-10-13 12:40:57 -04:00
function highlightBorder ( $element , highlight ) {
2024-11-27 05:20:23 -05:00
$element . toggleClass ( 'warning-input' , highlight )
return highlight
2014-08-14 12:48:34 -04:00
}
2024-11-27 05:20:23 -05:00
/ * *
*
* @ param $input
* /
2016-01-26 12:25:59 -05:00
function isInputValid ( $input ) {
2024-11-27 05:20:23 -05:00
const optional = $input . hasClass ( 'optional' )
2016-01-26 12:25:59 -05:00
switch ( $input . attr ( 'type' ) ) {
2024-11-27 05:20:23 -05:00
case 'text' :
case 'password' :
if ( $input . val ( ) === '' && ! optional ) {
return false
}
break
2016-01-26 12:25:59 -05:00
}
2024-11-27 05:20:23 -05:00
return true
2016-01-26 12:25:59 -05:00
}
2024-11-27 05:20:23 -05:00
/ * *
*
* @ param $input
* /
2014-10-13 12:40:57 -04:00
function highlightInput ( $input ) {
2016-01-26 12:25:59 -05:00
switch ( $input . attr ( 'type' ) ) {
2024-11-27 05:20:23 -05:00
case 'text' :
case 'password' :
return highlightBorder ( $input , ! isInputValid ( $input ) )
2014-08-14 12:48:34 -04:00
}
}
2014-10-31 06:41:07 -04:00
/ * *
* Initialize select2 plugin on the given elements
*
2024-11-27 05:20:23 -05:00
* @ param { Array < object > } array of jQuery elements
* @ param $elements
2022-01-10 08:06:28 -05:00
* @ param { number } userListLimit page size for result list
2014-10-31 06:41:07 -04:00
* /
2023-01-11 06:30:59 -05:00
function initApplicableUsersMultiselect ( $elements , userListLimit ) {
2024-11-27 05:20:23 -05:00
const escapeHTML = function ( text ) {
2020-10-07 16:58:00 -04:00
return text . toString ( )
. split ( '&' ) . join ( '&' )
. split ( '<' ) . join ( '<' )
. split ( '>' ) . join ( '>' )
. split ( '"' ) . join ( '"' )
2024-11-27 05:20:23 -05:00
. split ( '\'' ) . join ( ''' )
}
2014-10-31 06:41:07 -04:00
if ( ! $elements . length ) {
2024-11-27 05:20:23 -05:00
return
2014-10-31 06:41:07 -04:00
}
2023-01-11 06:30:59 -05:00
return $elements . select2 ( {
2022-09-21 11:44:32 -04:00
placeholder : t ( 'files_external' , 'Type to select account or group.' ) ,
2014-10-31 06:41:07 -04:00
allowClear : true ,
multiple : true ,
2017-07-08 09:56:19 -04:00
toggleSelect : true ,
2016-07-25 11:08:33 -04:00
dropdownCssClass : 'files-external-select2' ,
2024-11-27 05:20:23 -05:00
// minimumInputLength: 1,
2014-10-31 06:41:07 -04:00
ajax : {
url : OC . generateUrl ( 'apps/files_external/applicable' ) ,
dataType : 'json' ,
quietMillis : 100 ,
2024-11-27 05:20:23 -05:00
data ( term , page ) { // page is the one-based page number tracked by Select2
2014-10-31 06:41:07 -04:00
return {
2024-11-27 05:20:23 -05:00
pattern : term , // search term
2014-10-31 06:41:07 -04:00
limit : userListLimit , // page size
2024-11-27 05:20:23 -05:00
offset : userListLimit * ( page - 1 ) , // page number starts with 0
}
2014-10-31 06:41:07 -04:00
} ,
2024-11-27 05:20:23 -05:00
results ( data ) {
2014-10-31 06:41:07 -04:00
if ( data . status === 'success' ) {
2024-11-27 05:20:23 -05:00
const results = [ ]
let userCount = 0 // users is an object
2014-10-31 06:41:07 -04:00
// add groups
2020-11-30 08:39:13 -05:00
$ . each ( data . groups , function ( gid , group ) {
2024-11-27 05:20:23 -05:00
results . push ( { name : gid + '(group)' , displayname : group , type : 'group' } )
} )
2014-10-31 06:41:07 -04:00
// add users
$ . each ( data . users , function ( id , user ) {
2024-11-27 05:20:23 -05:00
userCount ++
results . push ( { name : id , displayname : user , type : 'user' } )
} )
2014-10-31 06:41:07 -04:00
2024-11-27 05:20:23 -05:00
const more = ( userCount >= userListLimit ) || ( data . groups . length >= userListLimit )
return { results , more }
2012-06-08 11:42:00 -04:00
} else {
2024-11-27 05:20:23 -05:00
// FIXME add error handling
2012-06-08 11:42:00 -04:00
}
2024-11-27 05:20:23 -05:00
} ,
2014-10-31 06:41:07 -04:00
} ,
2024-11-27 05:20:23 -05:00
initSelection ( element , callback ) {
const users = { }
users . users = [ ]
const toSplit = element . val ( ) . split ( ',' )
for ( let i = 0 ; i < toSplit . length ; i ++ ) {
users . users . push ( toSplit [ i ] )
2014-10-31 06:41:07 -04:00
}
$ . ajax ( OC . generateUrl ( 'displaynames' ) , {
type : 'POST' ,
contentType : 'application/json' ,
data : JSON . stringify ( users ) ,
2024-11-27 05:20:23 -05:00
dataType : 'json' ,
2014-10-31 06:41:07 -04:00
} ) . done ( function ( data ) {
2024-11-27 05:20:23 -05:00
const results = [ ]
2014-10-31 06:41:07 -04:00
if ( data . status === 'success' ) {
$ . each ( data . users , function ( user , displayname ) {
if ( displayname !== false ) {
2024-11-27 05:20:23 -05:00
results . push ( { name : user , displayname , type : 'user' } )
2013-10-10 05:10:32 -04:00
}
2024-11-27 05:20:23 -05:00
} )
callback ( results )
2014-10-31 06:41:07 -04:00
} else {
2024-11-27 05:20:23 -05:00
// FIXME add error handling
2014-10-31 06:41:07 -04:00
}
2024-11-27 05:20:23 -05:00
} )
2014-10-31 06:41:07 -04:00
} ,
2024-11-27 05:20:23 -05:00
id ( element ) {
return element . name
2014-10-31 06:41:07 -04:00
} ,
2024-11-27 05:20:23 -05:00
formatResult ( element ) {
const $result = $ ( '<span><div class="avatardiv"></div><span>' + escapeHTML ( element . displayname ) + '</span></span>' )
const $div = $result . find ( '.avatardiv' )
2014-10-31 06:41:07 -04:00
. attr ( 'data-type' , element . type )
. attr ( 'data-name' , element . name )
2024-11-27 05:20:23 -05:00
. attr ( 'data-displayname' , element . displayname )
2014-10-31 06:41:07 -04:00
if ( element . type === 'group' ) {
2024-11-27 05:20:23 -05:00
const url = OC . imagePath ( 'core' , 'actions/group' )
$div . html ( '<img width="32" height="32" src="' + url + '">' )
2014-10-31 06:41:07 -04:00
}
2024-11-27 05:20:23 -05:00
return $result . get ( 0 ) . outerHTML
2014-10-31 06:41:07 -04:00
} ,
2024-11-27 05:20:23 -05:00
formatSelection ( element ) {
2014-10-31 06:41:07 -04:00
if ( element . type === 'group' ) {
2024-11-27 05:20:23 -05:00
return '<span title="' + escapeHTML ( element . name ) + '" class="group">' + escapeHTML ( element . displayname + ' ' + t ( 'files_external' , '(Group)' ) ) + '</span>'
2012-06-08 11:42:00 -04:00
} else {
2024-11-27 05:20:23 -05:00
return '<span title="' + escapeHTML ( element . name ) + '" class="user">' + escapeHTML ( element . displayname ) + '</span>'
2012-06-08 11:42:00 -04:00
}
2014-10-31 06:41:07 -04:00
} ,
2024-11-27 05:20:23 -05:00
escapeMarkup ( m ) { return m } , // we escape the markup in formatResult and formatSelection
2014-10-31 06:41:07 -04:00
} ) . on ( 'select2-loaded' , function ( ) {
$ . each ( $ ( '.avatardiv' ) , function ( i , div ) {
2024-11-27 05:20:23 -05:00
const $div = $ ( div )
2014-10-31 06:41:07 -04:00
if ( $div . data ( 'type' ) === 'user' ) {
2024-11-27 05:20:23 -05:00
$div . avatar ( $div . data ( 'name' ) , 32 )
2014-10-31 06:41:07 -04:00
}
2024-11-27 05:20:23 -05:00
} )
2023-01-11 06:30:59 -05:00
} ) . on ( 'change' , function ( event ) {
2024-11-27 05:20:23 -05:00
highlightBorder ( $ ( event . target ) . closest ( '.applicableUsersContainer' ) . find ( '.select2-choices' ) , ! event . val . length )
} )
2014-10-31 06:41:07 -04:00
}
/ * *
2024-11-27 05:20:23 -05:00
* @ param id
2018-10-19 11:35:13 -04:00
* @ class OCA . Files _External . Settings . StorageConfig
2014-10-31 06:41:07 -04:00
*
* @ classdesc External storage config
* /
2024-11-27 05:20:23 -05:00
const StorageConfig = function ( id ) {
this . id = id
this . backendOptions = { }
}
2020-07-09 17:39:58 -04:00
// Keep this in sync with \OCA\Files_External\MountConfig::STATUS_*
2014-10-31 06:41:07 -04:00
StorageConfig . Status = {
IN _PROGRESS : - 1 ,
SUCCESS : 0 ,
2015-08-12 14:51:09 -04:00
ERROR : 1 ,
2024-11-27 05:20:23 -05:00
INDETERMINATE : 2 ,
}
2016-01-26 07:22:27 -05:00
StorageConfig . Visibility = {
NONE : 0 ,
PERSONAL : 1 ,
ADMIN : 2 ,
2024-11-27 05:20:23 -05:00
DEFAULT : 3 ,
}
2014-10-31 06:41:07 -04:00
/ * *
2018-10-19 11:35:13 -04:00
* @ memberof OCA . Files _External . Settings
2014-10-31 06:41:07 -04:00
* /
StorageConfig . prototype = {
_url : null ,
2012-06-12 11:36:25 -04:00
2014-10-31 06:41:07 -04:00
/ * *
* Storage id
*
* @ type int
* /
id : null ,
/ * *
* Mount point
*
* @ type string
* /
mountPoint : '' ,
/ * *
2015-08-12 15:03:11 -04:00
* Backend
2014-10-31 06:41:07 -04:00
*
* @ type string
* /
2015-08-12 15:03:11 -04:00
backend : null ,
2014-10-31 06:41:07 -04:00
Authentication mechanisms for external storage backends
A backend can now specify generic authentication schemes that it
supports, instead of specifying the parameters for its authentication
method directly. This allows multiple authentication mechanisms to be
implemented for a single scheme, providing altered functionality.
This commit introduces the backend framework for this feature, and so at
this point the UI will be broken as the frontend does not specify the
required information.
Terminology:
- authentication scheme
Parameter interface for the authentication method. A backend
supporting the 'password' scheme accepts two parameters, 'user' and
'password'.
- authentication mechanism
Specific mechanism implementing a scheme. Basic mechanisms may
forward configuration options directly to the backend, more advanced
ones may lookup parameters or retrieve them from the session
New dropdown selector for external storage configurations to select the
authentication mechanism to be used.
Authentication mechanisms can have visibilities, just like backends.
The API was extended too to make it easier to add/remove visibilities.
In addition, the concept of 'allowed visibility' has been introduced, so
a backend/auth mechanism can force a maximum visibility level (e.g.
Local storage type) that cannot be overridden by configuration in the
web UI.
An authentication mechanism is a fully instantiated implementation. This
allows an implementation to have dependencies injected into it, e.g. an
\OCP\IDB for database operations.
When a StorageConfig is being prepared for mounting, the authentication
mechanism implementation has manipulateStorage() called,
which inserts the relevant authentication method options into the
storage ready for mounting.
2015-08-12 05:54:03 -04:00
/ * *
2015-08-12 15:03:11 -04:00
* Authentication mechanism
Authentication mechanisms for external storage backends
A backend can now specify generic authentication schemes that it
supports, instead of specifying the parameters for its authentication
method directly. This allows multiple authentication mechanisms to be
implemented for a single scheme, providing altered functionality.
This commit introduces the backend framework for this feature, and so at
this point the UI will be broken as the frontend does not specify the
required information.
Terminology:
- authentication scheme
Parameter interface for the authentication method. A backend
supporting the 'password' scheme accepts two parameters, 'user' and
'password'.
- authentication mechanism
Specific mechanism implementing a scheme. Basic mechanisms may
forward configuration options directly to the backend, more advanced
ones may lookup parameters or retrieve them from the session
New dropdown selector for external storage configurations to select the
authentication mechanism to be used.
Authentication mechanisms can have visibilities, just like backends.
The API was extended too to make it easier to add/remove visibilities.
In addition, the concept of 'allowed visibility' has been introduced, so
a backend/auth mechanism can force a maximum visibility level (e.g.
Local storage type) that cannot be overridden by configuration in the
web UI.
An authentication mechanism is a fully instantiated implementation. This
allows an implementation to have dependencies injected into it, e.g. an
\OCP\IDB for database operations.
When a StorageConfig is being prepared for mounting, the authentication
mechanism implementation has manipulateStorage() called,
which inserts the relevant authentication method options into the
storage ready for mounting.
2015-08-12 05:54:03 -04:00
*
* @ type string
* /
2015-08-12 15:03:11 -04:00
authMechanism : null ,
Authentication mechanisms for external storage backends
A backend can now specify generic authentication schemes that it
supports, instead of specifying the parameters for its authentication
method directly. This allows multiple authentication mechanisms to be
implemented for a single scheme, providing altered functionality.
This commit introduces the backend framework for this feature, and so at
this point the UI will be broken as the frontend does not specify the
required information.
Terminology:
- authentication scheme
Parameter interface for the authentication method. A backend
supporting the 'password' scheme accepts two parameters, 'user' and
'password'.
- authentication mechanism
Specific mechanism implementing a scheme. Basic mechanisms may
forward configuration options directly to the backend, more advanced
ones may lookup parameters or retrieve them from the session
New dropdown selector for external storage configurations to select the
authentication mechanism to be used.
Authentication mechanisms can have visibilities, just like backends.
The API was extended too to make it easier to add/remove visibilities.
In addition, the concept of 'allowed visibility' has been introduced, so
a backend/auth mechanism can force a maximum visibility level (e.g.
Local storage type) that cannot be overridden by configuration in the
web UI.
An authentication mechanism is a fully instantiated implementation. This
allows an implementation to have dependencies injected into it, e.g. an
\OCP\IDB for database operations.
When a StorageConfig is being prepared for mounting, the authentication
mechanism implementation has manipulateStorage() called,
which inserts the relevant authentication method options into the
storage ready for mounting.
2015-08-12 05:54:03 -04:00
2014-10-31 06:41:07 -04:00
/ * *
* Backend - specific configuration
*
* @ type Object . < string , object >
* /
backendOptions : null ,
2015-03-13 07:49:11 -04:00
/ * *
* Mount - specific options
*
* @ type Object . < string , object >
* /
mountOptions : null ,
2014-10-31 06:41:07 -04:00
/ * *
* Creates or saves the storage .
*
* @ param { Function } [ options . success ] success callback , receives result as argument
* @ param { Function } [ options . error ] error callback
2024-11-27 05:20:23 -05:00
* @ param options
2014-10-31 06:41:07 -04:00
* /
2024-11-27 05:20:23 -05:00
save ( options ) {
let url = OC . generateUrl ( this . _url )
let method = 'POST'
2014-10-31 06:41:07 -04:00
if ( _ . isNumber ( this . id ) ) {
2024-11-27 05:20:23 -05:00
method = 'PUT'
url = OC . generateUrl ( this . _url + '/{id}' , { id : this . id } )
2014-10-31 06:41:07 -04:00
}
2024-11-27 05:20:23 -05:00
this . _save ( method , url , options )
2024-10-14 09:12:16 -04:00
} ,
/ * *
* Private implementation of the save function ( called after potential password confirmation )
2024-11-27 05:20:23 -05:00
* @ param { string } method
* @ param { string } url
* @ param { { success : Function , error : Function } } options
2024-10-14 09:12:16 -04:00
* /
2024-11-27 05:20:23 -05:00
async _save ( method , url , options ) {
2024-11-27 11:01:25 -05:00
try {
const response = await axios . request ( {
confirmPassword : PwdConfirmationMode . Strict ,
2024-11-27 05:20:23 -05:00
method ,
url ,
2024-11-27 11:01:25 -05:00
data : this . getData ( ) ,
} )
const result = response . data
this . id = result . id
options . success ( result )
} catch ( error ) {
options . error ( error )
}
2014-10-31 06:41:07 -04:00
} ,
/ * *
* Returns the data from this object
*
* @ return { Array } JSON array of the data
* /
2024-11-27 05:20:23 -05:00
getData ( ) {
const data = {
2014-10-31 06:41:07 -04:00
mountPoint : this . mountPoint ,
2015-08-12 15:03:11 -04:00
backend : this . backend ,
authMechanism : this . authMechanism ,
2016-06-07 10:53:16 -04:00
backendOptions : this . backendOptions ,
2024-11-27 05:20:23 -05:00
testOnly : true ,
}
2014-10-31 06:41:07 -04:00
if ( this . id ) {
2024-11-27 05:20:23 -05:00
data . id = this . id
2014-10-31 06:41:07 -04:00
}
2015-03-13 07:49:11 -04:00
if ( this . mountOptions ) {
2024-11-27 05:20:23 -05:00
data . mountOptions = this . mountOptions
2015-03-13 07:49:11 -04:00
}
2024-11-27 05:20:23 -05:00
return data
2014-10-31 06:41:07 -04:00
} ,
/ * *
* Recheck the storage
*
* @ param { Function } [ options . success ] success callback , receives result as argument
* @ param { Function } [ options . error ] error callback
2024-11-27 05:20:23 -05:00
* @ param options
2014-10-31 06:41:07 -04:00
* /
2024-11-27 05:20:23 -05:00
recheck ( options ) {
2014-10-31 06:41:07 -04:00
if ( ! _ . isNumber ( this . id ) ) {
if ( _ . isFunction ( options . error ) ) {
2024-11-27 05:20:23 -05:00
options . error ( )
2014-05-08 09:25:46 -04:00
}
2024-11-27 05:20:23 -05:00
return
2014-05-08 09:25:46 -04:00
}
2014-10-31 06:41:07 -04:00
$ . ajax ( {
type : 'GET' ,
2024-11-27 05:20:23 -05:00
url : OC . generateUrl ( this . _url + '/{id}' , { id : this . id } ) ,
data : { testOnly : true } ,
2014-10-31 06:41:07 -04:00
success : options . success ,
2024-11-27 05:20:23 -05:00
error : options . error ,
} )
2014-10-31 06:41:07 -04:00
} ,
2014-05-08 09:25:46 -04:00
2014-10-31 06:41:07 -04:00
/ * *
* Deletes the storage
*
* @ param { Function } [ options . success ] success callback
* @ param { Function } [ options . error ] error callback
2024-11-27 05:20:23 -05:00
* @ param options
2014-10-31 06:41:07 -04:00
* /
2024-11-27 05:20:23 -05:00
async destroy ( options ) {
2014-10-31 06:41:07 -04:00
if ( ! _ . isNumber ( this . id ) ) {
// the storage hasn't even been created => success
if ( _ . isFunction ( options . success ) ) {
2024-11-27 05:20:23 -05:00
options . success ( )
2014-10-31 06:41:07 -04:00
}
2024-11-27 05:20:23 -05:00
return
2014-10-31 06:41:07 -04:00
}
2024-10-14 09:12:16 -04:00
2024-11-27 11:01:25 -05:00
try {
await axios . request ( {
method : 'DELETE' ,
2024-11-27 05:20:23 -05:00
url : OC . generateUrl ( this . _url + '/{id}' , { id : this . id } ) ,
2024-11-27 11:01:25 -05:00
confirmPassword : PwdConfirmationMode . Strict ,
} )
options . success ( )
} catch ( e ) {
options . error ( e )
}
2014-10-31 06:41:07 -04:00
} ,
2014-08-22 12:16:55 -04:00
2014-10-31 06:41:07 -04:00
/ * *
* Validate this model
*
* @ return { boolean } false if errors exist , true otherwise
* /
2024-11-27 05:20:23 -05:00
validate ( ) {
2014-10-31 06:41:07 -04:00
if ( this . mountPoint === '' ) {
2024-11-27 05:20:23 -05:00
return false
2014-10-31 06:41:07 -04:00
}
2016-01-19 08:16:11 -05:00
if ( ! this . backend ) {
2024-11-27 05:20:23 -05:00
return false
2016-01-19 08:16:11 -05:00
}
2014-10-31 06:41:07 -04:00
if ( this . errors ) {
2024-11-27 05:20:23 -05:00
return false
2014-05-08 09:25:46 -04:00
}
2024-11-27 05:20:23 -05:00
return true
} ,
}
2014-10-31 06:41:07 -04:00
/ * *
2024-11-27 05:20:23 -05:00
* @ param id
2018-10-19 11:35:13 -04:00
* @ class OCA . Files _External . Settings . GlobalStorageConfig
* @ augments OCA . Files _External . Settings . StorageConfig
2014-10-31 06:41:07 -04:00
*
* @ classdesc Global external storage config
* /
2024-11-27 05:20:23 -05:00
const GlobalStorageConfig = function ( id ) {
this . id = id
this . applicableUsers = [ ]
this . applicableGroups = [ ]
}
2014-10-31 06:41:07 -04:00
/ * *
2018-10-19 11:35:13 -04:00
* @ memberOf OCA . Files _External . Settings
2014-10-31 06:41:07 -04:00
* /
GlobalStorageConfig . prototype = _ . extend ( { } , StorageConfig . prototype ,
2018-10-19 11:35:13 -04:00
/** @lends OCA.Files_External.Settings.GlobalStorageConfig.prototype */ {
2024-11-27 05:20:23 -05:00
_url : 'apps/files_external/globalstorages' ,
/ * *
* Applicable users
*
* @ type Array . < string >
* /
applicableUsers : null ,
/ * *
* Applicable groups
*
* @ type Array . < string >
* /
applicableGroups : null ,
/ * *
* Storage priority
*
* @ type int
* /
priority : null ,
/ * *
* Returns the data from this object
*
* @ return { Array } JSON array of the data
* /
getData ( ) {
const data = StorageConfig . prototype . getData . apply ( this , arguments )
return _ . extend ( data , {
applicableUsers : this . applicableUsers ,
applicableGroups : this . applicableGroups ,
priority : this . priority ,
} )
} ,
} )
2014-10-31 06:41:07 -04:00
/ * *
2024-11-27 05:20:23 -05:00
* @ param id
2018-10-19 11:35:13 -04:00
* @ class OCA . Files _External . Settings . UserStorageConfig
* @ augments OCA . Files _External . Settings . StorageConfig
2014-10-31 06:41:07 -04:00
*
* @ classdesc User external storage config
* /
2024-11-27 05:20:23 -05:00
const UserStorageConfig = function ( id ) {
this . id = id
}
2014-10-31 06:41:07 -04:00
UserStorageConfig . prototype = _ . extend ( { } , StorageConfig . prototype ,
2018-10-19 11:35:13 -04:00
/** @lends OCA.Files_External.Settings.UserStorageConfig.prototype */ {
2024-11-27 05:20:23 -05:00
_url : 'apps/files_external/userstorages' ,
} )
2014-10-31 06:41:07 -04:00
2016-01-19 08:16:11 -05:00
/ * *
2024-11-27 05:20:23 -05:00
* @ param id
2018-10-19 11:35:13 -04:00
* @ class OCA . Files _External . Settings . UserGlobalStorageConfig
* @ augments OCA . Files _External . Settings . StorageConfig
2016-01-19 08:16:11 -05:00
*
* @ classdesc User external storage config
* /
2024-11-27 05:20:23 -05:00
const UserGlobalStorageConfig = function ( id ) {
this . id = id
}
2016-01-19 08:16:11 -05:00
UserGlobalStorageConfig . prototype = _ . extend ( { } , StorageConfig . prototype ,
2018-10-19 11:35:13 -04:00
/** @lends OCA.Files_External.Settings.UserStorageConfig.prototype */ {
2016-01-19 08:16:11 -05:00
2024-11-27 05:20:23 -05:00
_url : 'apps/files_external/userglobalstorages' ,
} )
2016-01-19 08:16:11 -05:00
2015-03-16 09:07:53 -04:00
/ * *
2018-10-19 11:35:13 -04:00
* @ class OCA . Files _External . Settings . MountOptionsDropdown
2015-03-16 09:07:53 -04:00
*
* @ classdesc Dropdown for mount options
*
2024-11-27 05:20:23 -05:00
* @ param { object } $container container DOM object
2015-03-16 09:07:53 -04:00
* /
2024-11-27 05:20:23 -05:00
const MountOptionsDropdown = function ( ) {
}
2015-03-16 09:07:53 -04:00
/ * *
2018-10-19 11:35:13 -04:00
* @ memberof OCA . Files _External . Settings
2015-03-16 09:07:53 -04:00
* /
MountOptionsDropdown . prototype = {
/ * *
* Dropdown element
*
2024-11-27 05:20:23 -05:00
* @ member Object
2015-03-16 09:07:53 -04:00
* /
$el : null ,
/ * *
* Show dropdown
*
2024-11-27 05:20:23 -05:00
* @ param { object } $container container
* @ param { object } mountOptions mount options
2016-05-02 11:34:24 -04:00
* @ param { Array } visibleOptions enabled mount options
2015-03-16 09:07:53 -04:00
* /
2024-11-27 05:20:23 -05:00
show ( $container , mountOptions , visibleOptions ) {
2015-03-16 09:07:53 -04:00
if ( MountOptionsDropdown . _last ) {
2024-11-27 05:20:23 -05:00
MountOptionsDropdown . _last . hide ( )
2015-03-16 09:07:53 -04:00
}
2024-11-27 05:20:23 -05:00
const $el = $ ( OCA . Files _External . Templates . mountOptionsDropDown ( {
2018-10-19 02:41:01 -04:00
mountOptionsEncodingLabel : t ( 'files_external' , 'Compatibility with Mac NFD encoding (slow)' ) ,
mountOptionsEncryptLabel : t ( 'files_external' , 'Enable encryption' ) ,
mountOptionsPreviewsLabel : t ( 'files_external' , 'Enable previews' ) ,
mountOptionsSharingLabel : t ( 'files_external' , 'Enable sharing' ) ,
mountOptionsFilesystemCheckLabel : t ( 'files_external' , 'Check for changes' ) ,
mountOptionsFilesystemCheckOnce : t ( 'files_external' , 'Never' ) ,
mountOptionsFilesystemCheckDA : t ( 'files_external' , 'Once every direct access' ) ,
mountOptionsReadOnlyLabel : t ( 'files_external' , 'Read only' ) ,
2024-11-27 05:20:23 -05:00
deleteLabel : t ( 'files_external' , 'Disconnect' ) ,
} ) )
this . $el = $el
2015-03-16 09:07:53 -04:00
2024-11-27 05:20:23 -05:00
const storage = $container [ 0 ] . parentNode . className
2020-08-20 10:28:03 -04:00
2024-11-27 05:20:23 -05:00
this . setOptions ( mountOptions , visibleOptions , storage )
2015-03-16 09:07:53 -04:00
2024-11-27 05:20:23 -05:00
this . $el . appendTo ( $container )
MountOptionsDropdown . _last = this
2015-03-16 09:07:53 -04:00
2024-11-27 05:20:23 -05:00
this . $el . trigger ( 'show' )
2015-03-16 09:07:53 -04:00
} ,
2024-11-27 05:20:23 -05:00
hide ( ) {
2015-03-16 09:07:53 -04:00
if ( this . $el ) {
2024-11-27 05:20:23 -05:00
this . $el . trigger ( 'hide' )
this . $el . remove ( )
this . $el = null
MountOptionsDropdown . _last = null
2015-03-16 09:07:53 -04:00
}
} ,
/ * *
* Returns the mount options from the dropdown controls
*
2024-11-27 05:20:23 -05:00
* @ return { object } options mount options
2015-03-16 09:07:53 -04:00
* /
2024-11-27 05:20:23 -05:00
getOptions ( ) {
const options = { }
2015-03-16 09:07:53 -04:00
this . $el . find ( 'input, select' ) . each ( function ( ) {
2024-11-27 05:20:23 -05:00
const $this = $ ( this )
const key = $this . attr ( 'name' )
let value = null
2015-03-16 09:07:53 -04:00
if ( $this . attr ( 'type' ) === 'checkbox' ) {
2024-11-27 05:20:23 -05:00
value = $this . prop ( 'checked' )
2015-03-16 09:07:53 -04:00
} else {
2024-11-27 05:20:23 -05:00
value = $this . val ( )
2015-03-16 09:07:53 -04:00
}
if ( $this . attr ( 'data-type' ) === 'int' ) {
2024-11-27 05:20:23 -05:00
value = parseInt ( value , 10 )
2015-03-16 09:07:53 -04:00
}
2024-11-27 05:20:23 -05:00
options [ key ] = value
} )
return options
2015-03-16 09:07:53 -04:00
} ,
/ * *
* Sets the mount options to the dropdown controls
*
2024-11-27 05:20:23 -05:00
* @ param { object } options mount options
2016-05-02 11:34:24 -04:00
* @ param { Array } visibleOptions enabled mount options
2024-11-27 05:20:23 -05:00
* @ param storage
2015-03-16 09:07:53 -04:00
* /
2024-11-27 05:20:23 -05:00
setOptions ( options , visibleOptions , storage ) {
2020-08-20 10:28:03 -04:00
if ( storage === 'owncloud' ) {
2024-11-27 05:20:23 -05:00
const ind = visibleOptions . indexOf ( 'encrypt' )
2020-08-20 10:28:03 -04:00
if ( ind > 0 ) {
2024-11-27 05:20:23 -05:00
visibleOptions . splice ( ind , 1 )
2020-08-20 10:28:03 -04:00
}
}
2024-11-27 05:20:23 -05:00
const $el = this . $el
2015-03-16 09:07:53 -04:00
_ . each ( options , function ( value , key ) {
2024-11-27 05:20:23 -05:00
const $optionEl = $el . find ( 'input, select' ) . filterAttr ( 'name' , key )
2015-03-16 09:07:53 -04:00
if ( $optionEl . attr ( 'type' ) === 'checkbox' ) {
if ( _ . isString ( value ) ) {
2024-11-27 05:20:23 -05:00
value = ( value === 'true' )
2015-03-16 09:07:53 -04:00
}
2024-11-27 05:20:23 -05:00
$optionEl . prop ( 'checked' , ! ! value )
2015-03-16 09:07:53 -04:00
} else {
2024-11-27 05:20:23 -05:00
$optionEl . val ( value )
2015-03-16 09:07:53 -04:00
}
2024-11-27 05:20:23 -05:00
} )
$el . find ( '.optionRow' ) . each ( function ( i , row ) {
const $row = $ ( row )
const optionId = $row . find ( 'input, select' ) . attr ( 'name' )
2018-08-01 03:19:52 -04:00
if ( visibleOptions . indexOf ( optionId ) === - 1 && ! $row . hasClass ( 'persistent' ) ) {
2024-11-27 05:20:23 -05:00
$row . hide ( )
2015-04-02 12:31:26 -04:00
} else {
2024-11-27 05:20:23 -05:00
$row . show ( )
2015-03-31 10:25:33 -04:00
}
2024-11-27 05:20:23 -05:00
} )
} ,
}
2015-03-16 09:07:53 -04:00
2014-10-31 06:41:07 -04:00
/ * *
2018-10-19 11:35:13 -04:00
* @ class OCA . Files _External . Settings . MountConfigListView
2014-10-31 06:41:07 -04:00
*
* @ classdesc Mount configuration list view
*
2024-11-27 05:20:23 -05:00
* @ param { object } $el DOM object containing the list
* @ param { object } [ options ]
2022-01-10 08:06:28 -05:00
* @ param { number } [ options . userListLimit ] page size in applicable users dropdown
2014-10-31 06:41:07 -04:00
* /
2024-11-27 05:20:23 -05:00
const MountConfigListView = function ( $el , options ) {
this . initialize ( $el , options )
}
2016-02-01 11:44:58 -05:00
MountConfigListView . ParameterFlags = {
OPTIONAL : 1 ,
2024-11-27 05:20:23 -05:00
USER _PROVIDED : 2 ,
2025-05-05 04:52:28 -04:00
HIDDEN : 4 ,
2024-11-27 05:20:23 -05:00
}
2016-02-01 11:44:58 -05:00
MountConfigListView . ParameterTypes = {
TEXT : 0 ,
BOOLEAN : 1 ,
PASSWORD : 2 ,
2024-11-27 05:20:23 -05:00
}
2016-02-01 11:44:58 -05:00
2014-10-31 06:41:07 -04:00
/ * *
2018-10-19 11:35:13 -04:00
* @ memberOf OCA . Files _External . Settings
2014-10-31 06:41:07 -04:00
* /
2015-08-12 17:01:21 -04:00
MountConfigListView . prototype = _ . extend ( {
2014-10-31 06:41:07 -04:00
/ * *
* jQuery element containing the config list
*
* @ type Object
* /
$el : null ,
/ * *
* Storage config class
*
* @ type Class
* /
_storageConfigClass : null ,
/ * *
* Flag whether the list is about user storage configs ( true )
* or global storage configs ( false )
2015-03-15 15:47:22 -04:00
*
2014-10-31 06:41:07 -04:00
* @ type bool
* /
_isPersonal : false ,
/ * *
* Page size in applicable users dropdown
*
* @ type int
* /
_userListLimit : 30 ,
/ * *
* List of supported backends
*
* @ type Object . < string , Object >
* /
_allBackends : null ,
Authentication mechanisms for external storage backends
A backend can now specify generic authentication schemes that it
supports, instead of specifying the parameters for its authentication
method directly. This allows multiple authentication mechanisms to be
implemented for a single scheme, providing altered functionality.
This commit introduces the backend framework for this feature, and so at
this point the UI will be broken as the frontend does not specify the
required information.
Terminology:
- authentication scheme
Parameter interface for the authentication method. A backend
supporting the 'password' scheme accepts two parameters, 'user' and
'password'.
- authentication mechanism
Specific mechanism implementing a scheme. Basic mechanisms may
forward configuration options directly to the backend, more advanced
ones may lookup parameters or retrieve them from the session
New dropdown selector for external storage configurations to select the
authentication mechanism to be used.
Authentication mechanisms can have visibilities, just like backends.
The API was extended too to make it easier to add/remove visibilities.
In addition, the concept of 'allowed visibility' has been introduced, so
a backend/auth mechanism can force a maximum visibility level (e.g.
Local storage type) that cannot be overridden by configuration in the
web UI.
An authentication mechanism is a fully instantiated implementation. This
allows an implementation to have dependencies injected into it, e.g. an
\OCP\IDB for database operations.
When a StorageConfig is being prepared for mounting, the authentication
mechanism implementation has manipulateStorage() called,
which inserts the relevant authentication method options into the
storage ready for mounting.
2015-08-12 05:54:03 -04:00
/ * *
* List of all supported authentication mechanisms
*
* @ type Object . < string , Object >
* /
_allAuthMechanisms : null ,
2015-03-31 10:25:33 -04:00
_encryptionEnabled : false ,
2014-10-31 06:41:07 -04:00
/ * *
2024-11-27 05:20:23 -05:00
* @ param { object } $el DOM object containing the list
* @ param { object } [ options ]
2022-01-10 08:06:28 -05:00
* @ param { number } [ options . userListLimit ] page size in applicable users dropdown
2014-10-31 06:41:07 -04:00
* /
2024-11-27 05:20:23 -05:00
initialize ( $el , options ) {
this . $el = $el
this . _isPersonal = ( $el . data ( 'admin' ) !== true )
2014-10-31 06:41:07 -04:00
if ( this . _isPersonal ) {
2024-11-27 05:20:23 -05:00
this . _storageConfigClass = OCA . Files _External . Settings . UserStorageConfig
2014-10-31 06:41:07 -04:00
} else {
2024-11-27 05:20:23 -05:00
this . _storageConfigClass = OCA . Files _External . Settings . GlobalStorageConfig
2014-10-31 06:41:07 -04:00
}
if ( options && ! _ . isUndefined ( options . userListLimit ) ) {
2024-11-27 05:20:23 -05:00
this . _userListLimit = options . userListLimit
2014-10-31 06:41:07 -04:00
}
2024-11-27 05:20:23 -05:00
this . _encryptionEnabled = options . encryptionEnabled
this . _canCreateLocal = options . canCreateLocal
2015-03-31 10:25:33 -04:00
2014-10-31 06:41:07 -04:00
// read the backend config that was carefully crammed
// into the data-configurations attribute of the select
2024-11-27 05:20:23 -05:00
this . _allBackends = this . $el . find ( '.selectBackend' ) . data ( 'configurations' )
this . _allAuthMechanisms = this . $el . find ( '#addMountPoint .authentication' ) . data ( 'mechanisms' )
2014-10-31 06:41:07 -04:00
2024-11-27 05:20:23 -05:00
this . _initEvents ( )
2015-08-12 17:01:21 -04:00
} ,
2015-03-20 05:48:14 -04:00
2015-08-12 17:01:21 -04:00
/ * *
* Custom JS event handlers
* Trigger callback for all existing configurations
2024-11-27 05:20:23 -05:00
* @ param callback
2015-08-12 17:01:21 -04:00
* /
2024-11-27 05:20:23 -05:00
whenSelectBackend ( callback ) {
2022-07-07 12:19:03 -04:00
this . $el . find ( 'tbody tr:not(#addMountPoint):not(.externalStorageLoading)' ) . each ( function ( i , tr ) {
2024-11-27 05:20:23 -05:00
const backend = $ ( tr ) . find ( '.backend' ) . data ( 'identifier' )
callback ( $ ( tr ) , backend )
} )
this . on ( 'selectBackend' , callback )
2015-08-12 17:01:21 -04:00
} ,
2024-11-27 05:20:23 -05:00
whenSelectAuthMechanism ( callback ) {
const self = this
2022-07-07 12:19:03 -04:00
this . $el . find ( 'tbody tr:not(#addMountPoint):not(.externalStorageLoading)' ) . each ( function ( i , tr ) {
2024-11-27 05:20:23 -05:00
const authMechanism = $ ( tr ) . find ( '.selectAuthMechanism' ) . val ( )
callback ( $ ( tr ) , authMechanism , self . _allAuthMechanisms [ authMechanism ] . scheme )
} )
this . on ( 'selectAuthMechanism' , callback )
2014-10-31 06:41:07 -04:00
} ,
/ * *
* Initialize DOM event handlers
* /
2024-11-27 05:20:23 -05:00
_initEvents ( ) {
const self = this
2014-10-31 06:41:07 -04:00
2024-11-27 05:20:23 -05:00
const onChangeHandler = _ . bind ( this . _onChange , this )
// this.$el.on('input', 'td input', onChangeHandler);
this . $el . on ( 'keyup' , 'td input' , onChangeHandler )
this . $el . on ( 'paste' , 'td input' , onChangeHandler )
this . $el . on ( 'change' , 'td input:checkbox' , onChangeHandler )
this . $el . on ( 'change' , '.applicable' , onChangeHandler )
2014-10-31 06:41:07 -04:00
this . $el . on ( 'click' , '.status>span' , function ( ) {
2024-11-27 05:20:23 -05:00
self . recheckStorageConfig ( $ ( this ) . closest ( 'tr' ) )
} )
2014-10-31 06:41:07 -04:00
2018-08-01 03:19:52 -04:00
this . $el . on ( 'click' , 'td.mountOptionsToggle .icon-delete' , function ( ) {
2024-11-27 05:20:23 -05:00
self . deleteStorageConfig ( $ ( this ) . closest ( 'tr' ) )
} )
2014-10-31 06:41:07 -04:00
2024-11-27 05:20:23 -05:00
this . $el . on ( 'click' , 'td.save>.icon-checkmark' , function ( ) {
self . saveStorageConfig ( $ ( this ) . closest ( 'tr' ) )
} )
2017-03-22 09:32:26 -04:00
2018-08-01 03:19:52 -04:00
this . $el . on ( 'click' , 'td.mountOptionsToggle>.icon-more' , function ( ) {
2024-11-27 05:20:23 -05:00
$ ( this ) . attr ( 'aria-expanded' , 'true' )
self . _showMountOptionsDropdown ( $ ( this ) . closest ( 'tr' ) )
} )
2015-03-16 09:07:53 -04:00
2024-11-27 05:20:23 -05:00
this . $el . on ( 'change' , '.selectBackend' , _ . bind ( this . _onSelectBackend , this ) )
this . $el . on ( 'change' , '.selectAuthMechanism' , _ . bind ( this . _onSelectAuthMechanism , this ) )
2023-01-11 06:30:59 -05:00
2024-11-27 05:20:23 -05:00
this . $el . on ( 'change' , '.applicableToAllUsers' , _ . bind ( this . _onChangeApplicableToAllUsers , this ) )
2014-10-31 06:41:07 -04:00
} ,
2024-11-27 05:20:23 -05:00
_onChange ( event ) {
const $target = $ ( event . target )
2015-03-16 09:07:53 -04:00
if ( $target . closest ( '.dropdown' ) . length ) {
// ignore dropdown events
2024-11-27 05:20:23 -05:00
return
2015-03-16 09:07:53 -04:00
}
2024-11-27 05:20:23 -05:00
highlightInput ( $target )
const $tr = $target . closest ( 'tr' )
this . updateStatus ( $tr , null )
2015-03-15 15:47:22 -04:00
} ,
2024-11-27 05:20:23 -05:00
_onSelectBackend ( event ) {
const $target = $ ( event . target )
let $tr = $target . closest ( 'tr' )
2015-08-12 17:01:21 -04:00
2024-11-27 05:20:23 -05:00
const storageConfig = new this . _storageConfigClass ( )
storageConfig . mountPoint = $tr . find ( '.mountPoint input' ) . val ( )
storageConfig . backend = $target . val ( )
$tr . find ( '.mountPoint input' ) . val ( '' )
Authentication mechanisms for external storage backends
A backend can now specify generic authentication schemes that it
supports, instead of specifying the parameters for its authentication
method directly. This allows multiple authentication mechanisms to be
implemented for a single scheme, providing altered functionality.
This commit introduces the backend framework for this feature, and so at
this point the UI will be broken as the frontend does not specify the
required information.
Terminology:
- authentication scheme
Parameter interface for the authentication method. A backend
supporting the 'password' scheme accepts two parameters, 'user' and
'password'.
- authentication mechanism
Specific mechanism implementing a scheme. Basic mechanisms may
forward configuration options directly to the backend, more advanced
ones may lookup parameters or retrieve them from the session
New dropdown selector for external storage configurations to select the
authentication mechanism to be used.
Authentication mechanisms can have visibilities, just like backends.
The API was extended too to make it easier to add/remove visibilities.
In addition, the concept of 'allowed visibility' has been introduced, so
a backend/auth mechanism can force a maximum visibility level (e.g.
Local storage type) that cannot be overridden by configuration in the
web UI.
An authentication mechanism is a fully instantiated implementation. This
allows an implementation to have dependencies injected into it, e.g. an
\OCP\IDB for database operations.
When a StorageConfig is being prepared for mounting, the authentication
mechanism implementation has manipulateStorage() called,
which inserts the relevant authentication method options into the
storage ready for mounting.
2015-08-12 05:54:03 -04:00
2024-07-29 06:21:13 -04:00
$tr . find ( '.selectBackend' ) . prop ( 'selectedIndex' , 0 )
2024-11-27 05:20:23 -05:00
const onCompletion = jQuery . Deferred ( )
$tr = this . newStorage ( storageConfig , onCompletion )
$tr . find ( '.applicableToAllUsers' ) . prop ( 'checked' , false ) . trigger ( 'change' )
onCompletion . resolve ( )
2015-08-19 15:04:22 -04:00
2024-11-27 05:20:23 -05:00
$tr . find ( 'td.configuration' ) . children ( ) . not ( '[type=hidden]' ) . first ( ) . focus ( )
this . saveStorageConfig ( $tr )
2014-10-31 06:41:07 -04:00
} ,
2024-11-27 05:20:23 -05:00
_onSelectAuthMechanism ( event ) {
const $target = $ ( event . target )
const $tr = $target . closest ( 'tr' )
const authMechanism = $target . val ( )
2015-11-22 12:25:32 -05:00
2024-11-27 05:20:23 -05:00
const onCompletion = jQuery . Deferred ( )
this . configureAuthMechanism ( $tr , authMechanism , onCompletion )
onCompletion . resolve ( )
2015-11-22 12:25:32 -05:00
2024-11-27 05:20:23 -05:00
this . saveStorageConfig ( $tr )
2015-11-22 12:25:32 -05:00
} ,
2024-11-27 05:20:23 -05:00
_onChangeApplicableToAllUsers ( event ) {
const $target = $ ( event . target )
const $tr = $target . closest ( 'tr' )
const checked = $target . is ( ':checked' )
2023-01-11 06:30:59 -05:00
2024-11-27 05:20:23 -05:00
$tr . find ( '.applicableUsersContainer' ) . toggleClass ( 'hidden' , checked )
2023-01-11 06:30:59 -05:00
if ( ! checked ) {
2024-11-27 05:20:23 -05:00
$tr . find ( '.applicableUsers' ) . select2 ( 'val' , '' , true )
2023-01-11 06:30:59 -05:00
}
2024-11-27 05:20:23 -05:00
this . saveStorageConfig ( $tr )
2023-01-11 06:30:59 -05:00
} ,
2015-11-22 12:25:32 -05:00
/ * *
* Configure the storage config with a new authentication mechanism
*
* @ param { jQuery } $tr config row
* @ param { string } authMechanism
* @ param { jQuery . Deferred } onCompletion
* /
2024-11-27 05:20:23 -05:00
configureAuthMechanism ( $tr , authMechanism , onCompletion ) {
const authMechanismConfiguration = this . _allAuthMechanisms [ authMechanism ]
const $td = $tr . find ( 'td.configuration' )
$td . find ( '.auth-param' ) . remove ( )
Authentication mechanisms for external storage backends
A backend can now specify generic authentication schemes that it
supports, instead of specifying the parameters for its authentication
method directly. This allows multiple authentication mechanisms to be
implemented for a single scheme, providing altered functionality.
This commit introduces the backend framework for this feature, and so at
this point the UI will be broken as the frontend does not specify the
required information.
Terminology:
- authentication scheme
Parameter interface for the authentication method. A backend
supporting the 'password' scheme accepts two parameters, 'user' and
'password'.
- authentication mechanism
Specific mechanism implementing a scheme. Basic mechanisms may
forward configuration options directly to the backend, more advanced
ones may lookup parameters or retrieve them from the session
New dropdown selector for external storage configurations to select the
authentication mechanism to be used.
Authentication mechanisms can have visibilities, just like backends.
The API was extended too to make it easier to add/remove visibilities.
In addition, the concept of 'allowed visibility' has been introduced, so
a backend/auth mechanism can force a maximum visibility level (e.g.
Local storage type) that cannot be overridden by configuration in the
web UI.
An authentication mechanism is a fully instantiated implementation. This
allows an implementation to have dependencies injected into it, e.g. an
\OCP\IDB for database operations.
When a StorageConfig is being prepared for mounting, the authentication
mechanism implementation has manipulateStorage() called,
which inserts the relevant authentication method options into the
storage ready for mounting.
2015-08-12 05:54:03 -04:00
2024-11-27 05:20:23 -05:00
$ . each ( authMechanismConfiguration . configuration , _ . partial (
this . writeParameterInput , $td , _ , _ , [ 'auth-param' ] ,
) . bind ( this ) )
2015-08-19 15:04:22 -04:00
2015-08-12 17:01:21 -04:00
this . trigger ( 'selectAuthMechanism' ,
2024-11-27 05:20:23 -05:00
$tr , authMechanism , authMechanismConfiguration . scheme , onCompletion ,
)
Authentication mechanisms for external storage backends
A backend can now specify generic authentication schemes that it
supports, instead of specifying the parameters for its authentication
method directly. This allows multiple authentication mechanisms to be
implemented for a single scheme, providing altered functionality.
This commit introduces the backend framework for this feature, and so at
this point the UI will be broken as the frontend does not specify the
required information.
Terminology:
- authentication scheme
Parameter interface for the authentication method. A backend
supporting the 'password' scheme accepts two parameters, 'user' and
'password'.
- authentication mechanism
Specific mechanism implementing a scheme. Basic mechanisms may
forward configuration options directly to the backend, more advanced
ones may lookup parameters or retrieve them from the session
New dropdown selector for external storage configurations to select the
authentication mechanism to be used.
Authentication mechanisms can have visibilities, just like backends.
The API was extended too to make it easier to add/remove visibilities.
In addition, the concept of 'allowed visibility' has been introduced, so
a backend/auth mechanism can force a maximum visibility level (e.g.
Local storage type) that cannot be overridden by configuration in the
web UI.
An authentication mechanism is a fully instantiated implementation. This
allows an implementation to have dependencies injected into it, e.g. an
\OCP\IDB for database operations.
When a StorageConfig is being prepared for mounting, the authentication
mechanism implementation has manipulateStorage() called,
which inserts the relevant authentication method options into the
storage ready for mounting.
2015-08-12 05:54:03 -04:00
} ,
2015-09-13 18:23:42 -04:00
/ * *
* Create a config row for a new storage
*
* @ param { StorageConfig } storageConfig storage config to pull values from
2015-11-22 12:25:32 -05:00
* @ param { jQuery . Deferred } onCompletion
2022-02-28 10:52:55 -05:00
* @ param { boolean } deferAppend
2015-09-13 18:23:42 -04:00
* @ return { jQuery } created row
* /
2024-11-27 05:20:23 -05:00
newStorage ( storageConfig , onCompletion , deferAppend ) {
let mountPoint = storageConfig . mountPoint
let backend = this . _allBackends [ storageConfig . backend ]
2015-09-13 18:23:42 -04:00
2016-11-03 07:14:44 -04:00
if ( ! backend ) {
backend = {
name : 'Unknown: ' + storageConfig . backend ,
2024-11-27 05:20:23 -05:00
invalid : true ,
}
2016-11-03 07:14:44 -04:00
}
2015-09-13 18:23:42 -04:00
// FIXME: Replace with a proper Handlebar template
2024-11-27 05:20:23 -05:00
const $template = this . $el . find ( 'tr#addMountPoint' )
const $tr = $template . clone ( )
2022-02-28 10:52:55 -05:00
if ( ! deferAppend ) {
2024-11-27 05:20:23 -05:00
$tr . insertBefore ( $template )
2022-02-28 10:52:55 -05:00
}
2015-09-13 18:23:42 -04:00
2024-11-27 05:20:23 -05:00
$tr . data ( 'storageConfig' , storageConfig )
$tr . show ( )
$tr . find ( 'td.mountOptionsToggle, td.save, td.remove' ) . removeClass ( 'hidden' )
$tr . find ( 'td' ) . last ( ) . removeAttr ( 'style' )
$tr . removeAttr ( 'id' )
$tr . find ( 'select#selectBackend' )
2022-02-28 10:52:55 -05:00
if ( ! deferAppend ) {
2024-11-27 05:20:23 -05:00
initApplicableUsersMultiselect ( $tr . find ( '.applicableUsers' ) , this . _userListLimit )
2022-02-28 10:52:55 -05:00
}
2015-09-13 18:23:42 -04:00
if ( storageConfig . id ) {
2024-11-27 05:20:23 -05:00
$tr . data ( 'id' , storageConfig . id )
2015-09-13 18:23:42 -04:00
}
2024-11-27 05:20:23 -05:00
$tr . find ( '.backend' ) . text ( backend . name )
2015-09-13 18:23:42 -04:00
if ( mountPoint === '' ) {
2024-11-27 05:20:23 -05:00
mountPoint = this . _suggestMountPoint ( backend . name )
2015-09-13 18:23:42 -04:00
}
2024-11-27 05:20:23 -05:00
$tr . find ( '.mountPoint input' ) . val ( mountPoint )
$tr . addClass ( backend . identifier )
$tr . find ( '.backend' ) . data ( 'identifier' , backend . identifier )
2015-09-13 18:23:42 -04:00
2021-12-06 05:18:59 -05:00
if ( backend . invalid || ( backend . identifier === 'local' && ! this . _canCreateLocal ) ) {
2024-11-27 05:20:23 -05:00
$tr . find ( '[name=mountPoint]' ) . prop ( 'disabled' , true )
$tr . find ( '.applicable,.mountOptionsToggle' ) . empty ( )
$tr . find ( '.save' ) . empty ( )
2021-12-06 05:18:59 -05:00
if ( backend . invalid ) {
2024-11-27 05:20:23 -05:00
this . updateStatus ( $tr , false , t ( 'files_external' , 'Unknown backend: {backendName}' , { backendName : backend . name } ) )
2021-12-06 05:18:59 -05:00
}
2024-11-27 05:20:23 -05:00
return $tr
2016-11-03 07:14:44 -04:00
}
2024-11-27 05:20:23 -05:00
const selectAuthMechanism = $ ( '<select class="selectAuthMechanism"></select>' )
const neededVisibility = ( this . _isPersonal ) ? StorageConfig . Visibility . PERSONAL : StorageConfig . Visibility . ADMIN
2015-09-13 18:23:42 -04:00
$ . each ( this . _allAuthMechanisms , function ( authIdentifier , authMechanism ) {
2016-01-19 08:21:59 -05:00
if ( backend . authSchemes [ authMechanism . scheme ] && ( authMechanism . visibility & neededVisibility ) ) {
2015-09-13 18:23:42 -04:00
selectAuthMechanism . append (
2024-11-27 05:20:23 -05:00
$ ( '<option value="' + authMechanism . identifier + '" data-scheme="' + authMechanism . scheme + '">' + authMechanism . name + '</option>' ) ,
)
2015-09-13 18:23:42 -04:00
}
2024-11-27 05:20:23 -05:00
} )
2015-09-13 18:23:42 -04:00
if ( storageConfig . authMechanism ) {
2024-11-27 05:20:23 -05:00
selectAuthMechanism . val ( storageConfig . authMechanism )
2015-11-22 12:25:32 -05:00
} else {
2024-11-27 05:20:23 -05:00
storageConfig . authMechanism = selectAuthMechanism . val ( )
2015-09-13 18:23:42 -04:00
}
2024-11-27 05:20:23 -05:00
$tr . find ( 'td.authentication' ) . append ( selectAuthMechanism )
2015-09-13 18:23:42 -04:00
2024-11-27 05:20:23 -05:00
const $td = $tr . find ( 'td.configuration' )
$ . each ( backend . configuration , _ . partial ( this . writeParameterInput , $td ) . bind ( this ) )
2015-09-13 18:23:42 -04:00
2024-11-27 05:20:23 -05:00
this . trigger ( 'selectBackend' , $tr , backend . identifier , onCompletion )
this . configureAuthMechanism ( $tr , storageConfig . authMechanism , onCompletion )
2015-09-13 18:23:42 -04:00
if ( storageConfig . backendOptions ) {
2016-06-15 09:24:01 -04:00
$td . find ( 'input, select' ) . each ( function ( ) {
2024-11-27 05:20:23 -05:00
const input = $ ( this )
const val = storageConfig . backendOptions [ input . data ( 'parameter' ) ]
2015-10-20 08:26:46 -04:00
if ( val !== undefined ) {
2024-11-27 05:20:23 -05:00
if ( input . is ( 'input:checkbox' ) ) {
input . prop ( 'checked' , val )
2016-02-24 13:49:03 -05:00
}
2024-11-27 05:20:23 -05:00
input . val ( storageConfig . backendOptions [ input . data ( 'parameter' ) ] )
highlightInput ( input )
2015-10-20 08:26:46 -04:00
}
2024-11-27 05:20:23 -05:00
} )
2015-09-13 18:23:42 -04:00
}
2024-11-27 05:20:23 -05:00
let applicable = [ ]
2015-09-14 15:21:16 -04:00
if ( storageConfig . applicableUsers ) {
2024-11-27 05:20:23 -05:00
applicable = applicable . concat ( storageConfig . applicableUsers )
2015-09-14 15:21:16 -04:00
}
if ( storageConfig . applicableGroups ) {
applicable = applicable . concat (
_ . map ( storageConfig . applicableGroups , function ( group ) {
2024-11-27 05:20:23 -05:00
return group + '(group)'
} ) ,
)
2015-09-14 15:21:16 -04:00
}
2023-01-11 06:30:59 -05:00
if ( applicable . length ) {
$tr . find ( '.applicableUsers' ) . val ( applicable ) . trigger ( 'change' )
2024-11-27 05:20:23 -05:00
$tr . find ( '.applicableUsersContainer' ) . removeClass ( 'hidden' )
2023-01-11 06:30:59 -05:00
} else {
// applicable to all
2024-11-27 05:20:23 -05:00
$tr . find ( '.applicableUsersContainer' ) . addClass ( 'hidden' )
2023-01-11 06:30:59 -05:00
}
2024-11-27 05:20:23 -05:00
$tr . find ( '.applicableToAllUsers' ) . prop ( 'checked' , ! applicable . length )
2015-09-14 15:21:16 -04:00
2024-11-27 05:20:23 -05:00
const priorityEl = $ ( '<input type="hidden" class="priority" value="' + backend . priority + '" />' )
$tr . append ( priorityEl )
2015-09-13 18:23:42 -04:00
if ( storageConfig . mountOptions ) {
2024-11-27 05:20:23 -05:00
$tr . find ( 'input.mountOptions' ) . val ( JSON . stringify ( storageConfig . mountOptions ) )
2015-09-13 18:23:42 -04:00
} else {
// FIXME default backend mount options
$tr . find ( 'input.mountOptions' ) . val ( JSON . stringify ( {
2024-11-27 05:20:23 -05:00
encrypt : true ,
previews : true ,
enable _sharing : false ,
filesystem _check _changes : 1 ,
encoding _compatibility : false ,
readonly : false ,
} ) )
2015-09-13 18:23:42 -04:00
}
2024-11-27 05:20:23 -05:00
return $tr
2015-09-13 18:23:42 -04:00
} ,
/ * *
* Load storages into config rows
* /
2024-11-27 05:20:23 -05:00
loadStorages ( ) {
const self = this
2015-09-13 18:23:42 -04:00
2024-11-27 05:20:23 -05:00
const onLoaded1 = $ . Deferred ( )
const onLoaded2 = $ . Deferred ( )
2021-12-06 06:41:55 -05:00
2024-11-27 05:20:23 -05:00
this . $el . find ( '.externalStorageLoading' ) . removeClass ( 'hidden' )
2021-12-06 06:41:55 -05:00
$ . when ( onLoaded1 , onLoaded2 ) . always ( ( ) => {
2024-11-27 05:20:23 -05:00
self . $el . find ( '.externalStorageLoading' ) . addClass ( 'hidden' )
2021-12-06 06:41:55 -05:00
} )
2015-09-14 08:57:49 -04:00
if ( this . _isPersonal ) {
// load userglobal storages
$ . ajax ( {
type : 'GET' ,
url : OC . generateUrl ( 'apps/files_external/userglobalstorages' ) ,
2024-11-27 05:20:23 -05:00
data : { testOnly : true } ,
2015-09-14 08:57:49 -04:00
contentType : 'application/json' ,
2024-11-27 05:20:23 -05:00
success ( result ) {
result = Object . values ( result )
const onCompletion = jQuery . Deferred ( )
let $rows = $ ( )
2024-07-29 06:08:55 -04:00
result . forEach ( function ( storageParams ) {
2024-11-27 05:20:23 -05:00
let storageConfig
const isUserGlobal = storageParams . type === 'system' && self . _isPersonal
storageParams . mountPoint = storageParams . mountPoint . substr ( 1 ) // trim leading slash
2016-01-19 10:57:20 -05:00
if ( isUserGlobal ) {
2024-11-27 05:20:23 -05:00
storageConfig = new UserGlobalStorageConfig ( )
2016-01-19 08:16:11 -05:00
} else {
2024-11-27 05:20:23 -05:00
storageConfig = new self . _storageConfigClass ( )
2016-01-19 08:16:11 -05:00
}
2024-11-27 05:20:23 -05:00
_ . extend ( storageConfig , storageParams )
const $tr = self . newStorage ( storageConfig , onCompletion , true )
2015-09-14 08:57:49 -04:00
// userglobal storages must be at the top of the list
2024-11-27 05:20:23 -05:00
$tr . detach ( )
self . $el . prepend ( $tr )
2015-09-14 08:57:49 -04:00
2024-11-27 05:20:23 -05:00
const $authentication = $tr . find ( '.authentication' )
$authentication . text ( $authentication . find ( 'select option:selected' ) . text ( ) )
2015-09-14 08:57:49 -04:00
// disable any other inputs
2024-11-27 05:20:23 -05:00
$tr . find ( '.mountOptionsToggle, .remove' ) . empty ( )
$tr . find ( 'input:not(.user_provided), select:not(.user_provided)' ) . attr ( 'disabled' , 'disabled' )
2016-01-19 08:16:11 -05:00
2016-01-19 10:57:20 -05:00
if ( isUserGlobal ) {
2024-11-27 05:20:23 -05:00
$tr . find ( '.configuration' ) . find ( ':not(.user_provided)' ) . remove ( )
2016-01-19 08:16:11 -05:00
} else {
// userglobal storages do not expose configuration data
2024-11-27 05:20:23 -05:00
$tr . find ( '.configuration' ) . text ( t ( 'files_external' , 'Admin defined' ) )
2016-01-19 08:16:11 -05:00
}
2024-07-29 06:09:38 -04:00
// don't recheck config automatically when there are a large number of storages
if ( result . length < 20 ) {
2024-11-27 05:20:23 -05:00
self . recheckStorageConfig ( $tr )
2024-07-29 06:09:38 -04:00
} else {
2024-11-27 05:20:23 -05:00
self . updateStatus ( $tr , StorageConfig . Status . INDETERMINATE , t ( 'files_external' , 'Automatic status checking is disabled due to the large number of configured storages, click to check status' ) )
2024-07-29 06:09:38 -04:00
}
2024-11-27 05:20:23 -05:00
$rows = $rows . add ( $tr )
} )
initApplicableUsersMultiselect ( self . $el . find ( '.applicableUsers' ) , this . _userListLimit )
self . $el . find ( 'tr#addMountPoint' ) . before ( $rows )
const mainForm = $ ( '#files_external' )
2016-08-09 08:19:15 -04:00
if ( result . length === 0 && mainForm . attr ( 'data-can-create' ) === 'false' ) {
2024-11-27 05:20:23 -05:00
mainForm . hide ( )
$ ( 'a[href="#external-storage"]' ) . parent ( ) . hide ( )
$ ( '.emptycontent' ) . show ( )
2016-08-09 08:19:15 -04:00
}
2024-11-27 05:20:23 -05:00
onCompletion . resolve ( )
onLoaded1 . resolve ( )
} ,
} )
2021-12-06 06:41:55 -05:00
} else {
2024-11-27 05:20:23 -05:00
onLoaded1 . resolve ( )
2015-09-14 08:57:49 -04:00
}
2024-11-27 05:20:23 -05:00
const url = this . _storageConfigClass . prototype . _url
2015-09-13 18:23:42 -04:00
$ . ajax ( {
type : 'GET' ,
url : OC . generateUrl ( url ) ,
contentType : 'application/json' ,
2024-11-27 05:20:23 -05:00
success ( result ) {
result = Object . values ( result )
const onCompletion = jQuery . Deferred ( )
let $rows = $ ( )
2022-02-28 11:02:17 -05:00
result . forEach ( function ( storageParams ) {
2024-11-27 05:20:23 -05:00
storageParams . mountPoint = ( storageParams . mountPoint === '/' ) ? '/' : storageParams . mountPoint . substr ( 1 ) // trim leading slash
const storageConfig = new self . _storageConfigClass ( )
_ . extend ( storageConfig , storageParams )
const $tr = self . newStorage ( storageConfig , onCompletion , true )
2021-12-06 05:18:59 -05:00
2022-02-28 11:02:17 -05:00
// don't recheck config automatically when there are a large number of storages
if ( result . length < 20 ) {
2024-11-27 05:20:23 -05:00
self . recheckStorageConfig ( $tr )
2022-02-28 11:02:17 -05:00
} else {
2024-11-27 05:20:23 -05:00
self . updateStatus ( $tr , StorageConfig . Status . INDETERMINATE , t ( 'files_external' , 'Automatic status checking is disabled due to the large number of configured storages, click to check status' ) )
2022-02-28 11:02:17 -05:00
}
2024-11-27 05:20:23 -05:00
$rows = $rows . add ( $tr )
} )
initApplicableUsersMultiselect ( $rows . find ( '.applicableUsers' ) , this . _userListLimit )
self . $el . find ( 'tr#addMountPoint' ) . before ( $rows )
onCompletion . resolve ( )
onLoaded2 . resolve ( )
} ,
} )
2015-09-13 18:23:42 -04:00
} ,
/ * *
* @ param { jQuery } $td
* @ param { string } parameter
* @ param { string } placeholder
* @ param { Array } classes
* @ return { jQuery } newly created input
* /
2024-11-27 05:20:23 -05:00
writeParameterInput ( $td , parameter , placeholder , classes ) {
const hasFlag = function ( flag ) {
return ( placeholder . flags & flag ) === flag
}
classes = $ . isArray ( classes ) ? classes : [ ]
classes . push ( 'added' )
2016-02-01 11:44:58 -05:00
if ( hasFlag ( MountConfigListView . ParameterFlags . OPTIONAL ) ) {
2024-11-27 05:20:23 -05:00
classes . push ( 'optional' )
Authentication mechanisms for external storage backends
A backend can now specify generic authentication schemes that it
supports, instead of specifying the parameters for its authentication
method directly. This allows multiple authentication mechanisms to be
implemented for a single scheme, providing altered functionality.
This commit introduces the backend framework for this feature, and so at
this point the UI will be broken as the frontend does not specify the
required information.
Terminology:
- authentication scheme
Parameter interface for the authentication method. A backend
supporting the 'password' scheme accepts two parameters, 'user' and
'password'.
- authentication mechanism
Specific mechanism implementing a scheme. Basic mechanisms may
forward configuration options directly to the backend, more advanced
ones may lookup parameters or retrieve them from the session
New dropdown selector for external storage configurations to select the
authentication mechanism to be used.
Authentication mechanisms can have visibilities, just like backends.
The API was extended too to make it easier to add/remove visibilities.
In addition, the concept of 'allowed visibility' has been introduced, so
a backend/auth mechanism can force a maximum visibility level (e.g.
Local storage type) that cannot be overridden by configuration in the
web UI.
An authentication mechanism is a fully instantiated implementation. This
allows an implementation to have dependencies injected into it, e.g. an
\OCP\IDB for database operations.
When a StorageConfig is being prepared for mounting, the authentication
mechanism implementation has manipulateStorage() called,
which inserts the relevant authentication method options into the
storage ready for mounting.
2015-08-12 05:54:03 -04:00
}
2016-01-19 08:16:11 -05:00
2016-02-01 11:44:58 -05:00
if ( hasFlag ( MountConfigListView . ParameterFlags . USER _PROVIDED ) ) {
2016-01-19 08:16:11 -05:00
if ( this . _isPersonal ) {
2024-11-27 05:20:23 -05:00
classes . push ( 'user_provided' )
2016-01-19 08:16:11 -05:00
} else {
2024-11-27 05:20:23 -05:00
return
2016-01-19 08:16:11 -05:00
}
}
2024-11-27 05:20:23 -05:00
let newElement
2016-01-19 08:16:11 -05:00
2024-11-27 05:20:23 -05:00
const trimmedPlaceholder = placeholder . value
2025-05-05 04:52:28 -04:00
if ( hasFlag ( MountConfigListView . ParameterFlags . HIDDEN ) ) {
newElement = $ ( '<input type="hidden" class="' + classes . join ( ' ' ) + '" data-parameter="' + parameter + '" />' )
} else if ( placeholder . type === MountConfigListView . ParameterTypes . PASSWORD ) {
2024-11-27 05:20:23 -05:00
newElement = $ ( '<input type="password" class="' + classes . join ( ' ' ) + '" data-parameter="' + parameter + '" placeholder="' + trimmedPlaceholder + '" />' )
2016-02-01 11:44:58 -05:00
} else if ( placeholder . type === MountConfigListView . ParameterTypes . BOOLEAN ) {
2024-11-27 05:20:23 -05:00
const checkboxId = _ . uniqueId ( 'checkbox_' )
newElement = $ ( '<div><label><input type="checkbox" id="' + checkboxId + '" class="' + classes . join ( ' ' ) + '" data-parameter="' + parameter + '" />' + trimmedPlaceholder + '</label></div>' )
Authentication mechanisms for external storage backends
A backend can now specify generic authentication schemes that it
supports, instead of specifying the parameters for its authentication
method directly. This allows multiple authentication mechanisms to be
implemented for a single scheme, providing altered functionality.
This commit introduces the backend framework for this feature, and so at
this point the UI will be broken as the frontend does not specify the
required information.
Terminology:
- authentication scheme
Parameter interface for the authentication method. A backend
supporting the 'password' scheme accepts two parameters, 'user' and
'password'.
- authentication mechanism
Specific mechanism implementing a scheme. Basic mechanisms may
forward configuration options directly to the backend, more advanced
ones may lookup parameters or retrieve them from the session
New dropdown selector for external storage configurations to select the
authentication mechanism to be used.
Authentication mechanisms can have visibilities, just like backends.
The API was extended too to make it easier to add/remove visibilities.
In addition, the concept of 'allowed visibility' has been introduced, so
a backend/auth mechanism can force a maximum visibility level (e.g.
Local storage type) that cannot be overridden by configuration in the
web UI.
An authentication mechanism is a fully instantiated implementation. This
allows an implementation to have dependencies injected into it, e.g. an
\OCP\IDB for database operations.
When a StorageConfig is being prepared for mounting, the authentication
mechanism implementation has manipulateStorage() called,
which inserts the relevant authentication method options into the
storage ready for mounting.
2015-08-12 05:54:03 -04:00
} else {
2024-11-27 05:20:23 -05:00
newElement = $ ( '<input type="text" class="' + classes . join ( ' ' ) + '" data-parameter="' + parameter + '" placeholder="' + trimmedPlaceholder + '" />' )
Authentication mechanisms for external storage backends
A backend can now specify generic authentication schemes that it
supports, instead of specifying the parameters for its authentication
method directly. This allows multiple authentication mechanisms to be
implemented for a single scheme, providing altered functionality.
This commit introduces the backend framework for this feature, and so at
this point the UI will be broken as the frontend does not specify the
required information.
Terminology:
- authentication scheme
Parameter interface for the authentication method. A backend
supporting the 'password' scheme accepts two parameters, 'user' and
'password'.
- authentication mechanism
Specific mechanism implementing a scheme. Basic mechanisms may
forward configuration options directly to the backend, more advanced
ones may lookup parameters or retrieve them from the session
New dropdown selector for external storage configurations to select the
authentication mechanism to be used.
Authentication mechanisms can have visibilities, just like backends.
The API was extended too to make it easier to add/remove visibilities.
In addition, the concept of 'allowed visibility' has been introduced, so
a backend/auth mechanism can force a maximum visibility level (e.g.
Local storage type) that cannot be overridden by configuration in the
web UI.
An authentication mechanism is a fully instantiated implementation. This
allows an implementation to have dependencies injected into it, e.g. an
\OCP\IDB for database operations.
When a StorageConfig is being prepared for mounting, the authentication
mechanism implementation has manipulateStorage() called,
which inserts the relevant authentication method options into the
storage ready for mounting.
2015-08-12 05:54:03 -04:00
}
2020-04-02 11:22:42 -04:00
2023-01-11 08:39:39 -05:00
if ( placeholder . defaultValue ) {
if ( placeholder . type === MountConfigListView . ParameterTypes . BOOLEAN ) {
2024-11-27 05:20:23 -05:00
newElement . find ( 'input' ) . prop ( 'checked' , placeholder . defaultValue )
2023-01-11 08:39:39 -05:00
} else {
2024-11-27 05:20:23 -05:00
newElement . val ( placeholder . defaultValue )
2023-01-11 08:39:39 -05:00
}
}
2020-04-02 11:22:42 -04:00
if ( placeholder . tooltip ) {
2024-11-27 05:20:23 -05:00
newElement . attr ( 'title' , placeholder . tooltip )
2020-04-02 11:22:42 -04:00
}
2024-11-27 05:20:23 -05:00
highlightInput ( newElement )
$td . append ( newElement )
return newElement
Authentication mechanisms for external storage backends
A backend can now specify generic authentication schemes that it
supports, instead of specifying the parameters for its authentication
method directly. This allows multiple authentication mechanisms to be
implemented for a single scheme, providing altered functionality.
This commit introduces the backend framework for this feature, and so at
this point the UI will be broken as the frontend does not specify the
required information.
Terminology:
- authentication scheme
Parameter interface for the authentication method. A backend
supporting the 'password' scheme accepts two parameters, 'user' and
'password'.
- authentication mechanism
Specific mechanism implementing a scheme. Basic mechanisms may
forward configuration options directly to the backend, more advanced
ones may lookup parameters or retrieve them from the session
New dropdown selector for external storage configurations to select the
authentication mechanism to be used.
Authentication mechanisms can have visibilities, just like backends.
The API was extended too to make it easier to add/remove visibilities.
In addition, the concept of 'allowed visibility' has been introduced, so
a backend/auth mechanism can force a maximum visibility level (e.g.
Local storage type) that cannot be overridden by configuration in the
web UI.
An authentication mechanism is a fully instantiated implementation. This
allows an implementation to have dependencies injected into it, e.g. an
\OCP\IDB for database operations.
When a StorageConfig is being prepared for mounting, the authentication
mechanism implementation has manipulateStorage() called,
which inserts the relevant authentication method options into the
storage ready for mounting.
2015-08-12 05:54:03 -04:00
} ,
2014-10-31 06:41:07 -04:00
/ * *
* Gets the storage model from the given row
*
* @ param $tr row element
2018-10-19 11:35:13 -04:00
* @ return { OCA . Files _External . StorageConfig } storage model instance
2014-10-31 06:41:07 -04:00
* /
2024-11-27 05:20:23 -05:00
getStorageConfig ( $tr ) {
let storageId = $tr . data ( 'id' )
2014-10-31 06:41:07 -04:00
if ( ! storageId ) {
// new entry
2024-11-27 05:20:23 -05:00
storageId = null
2014-10-31 06:41:07 -04:00
}
2016-01-19 08:16:11 -05:00
2024-11-27 05:20:23 -05:00
let storage = $tr . data ( 'storageConfig' )
2016-01-19 08:16:11 -05:00
if ( ! storage ) {
2024-11-27 05:20:23 -05:00
storage = new this . _storageConfigClass ( storageId )
2016-01-19 08:16:11 -05:00
}
2024-11-27 05:20:23 -05:00
storage . errors = null
storage . mountPoint = $tr . find ( '.mountPoint input' ) . val ( )
storage . backend = $tr . find ( '.backend' ) . data ( 'identifier' )
storage . authMechanism = $tr . find ( '.selectAuthMechanism' ) . val ( )
const classOptions = { }
const configuration = $tr . find ( '.configuration input' )
const missingOptions = [ ]
2014-10-31 06:41:07 -04:00
$ . each ( configuration , function ( index , input ) {
2024-11-27 05:20:23 -05:00
const $input = $ ( input )
const parameter = $input . data ( 'parameter' )
2014-10-31 06:41:07 -04:00
if ( $input . attr ( 'type' ) === 'button' ) {
2024-11-27 05:20:23 -05:00
return
2014-10-31 06:41:07 -04:00
}
2016-01-19 08:16:11 -05:00
if ( ! isInputValid ( $input ) && ! $input . hasClass ( 'optional' ) ) {
2024-11-27 05:20:23 -05:00
missingOptions . push ( parameter )
return
2014-10-31 06:41:07 -04:00
}
if ( $ ( input ) . is ( ':checkbox' ) ) {
if ( $ ( input ) . is ( ':checked' ) ) {
2024-11-27 05:20:23 -05:00
classOptions [ parameter ] = true
2014-10-31 06:41:07 -04:00
} else {
2024-11-27 05:20:23 -05:00
classOptions [ parameter ] = false
2014-10-31 06:41:07 -04:00
}
} else {
2024-11-27 05:20:23 -05:00
classOptions [ parameter ] = $ ( input ) . val ( )
2014-10-31 06:41:07 -04:00
}
2024-11-27 05:20:23 -05:00
} )
2014-10-31 06:41:07 -04:00
2024-11-27 05:20:23 -05:00
storage . backendOptions = classOptions
2014-10-31 06:41:07 -04:00
if ( missingOptions . length ) {
storage . errors = {
2024-11-27 05:20:23 -05:00
backendOptions : missingOptions ,
}
2014-10-31 06:41:07 -04:00
}
// gather selected users and groups
if ( ! this . _isPersonal ) {
2024-11-27 05:20:23 -05:00
const multiselect = getSelectedApplicable ( $tr )
const users = multiselect . users || [ ]
const groups = multiselect . groups || [ ]
const isApplicableToAllUsers = $tr . find ( '.applicableToAllUsers' ) . is ( ':checked' )
2023-01-11 06:30:59 -05:00
if ( isApplicableToAllUsers ) {
2024-11-27 05:20:23 -05:00
storage . applicableUsers = [ ]
storage . applicableGroups = [ ]
2023-01-11 06:30:59 -05:00
} else {
2024-11-27 05:20:23 -05:00
storage . applicableUsers = users
storage . applicableGroups = groups
2014-10-31 06:41:07 -04:00
2023-01-11 06:30:59 -05:00
if ( ! storage . applicableUsers . length && ! storage . applicableGroups . length ) {
if ( ! storage . errors ) {
2024-11-27 05:20:23 -05:00
storage . errors = { }
2023-01-11 06:30:59 -05:00
}
2024-11-27 05:20:23 -05:00
storage . errors . requiredApplicable = true
2023-01-11 06:30:59 -05:00
}
}
2015-03-12 12:27:31 -04:00
2024-11-27 05:20:23 -05:00
storage . priority = parseInt ( $tr . find ( 'input.priority' ) . val ( ) || '100' , 10 )
2014-10-31 06:41:07 -04:00
}
2024-11-27 05:20:23 -05:00
const mountOptions = $tr . find ( 'input.mountOptions' ) . val ( )
2015-03-13 07:49:11 -04:00
if ( mountOptions ) {
2024-11-27 05:20:23 -05:00
storage . mountOptions = JSON . parse ( mountOptions )
2015-03-13 07:49:11 -04:00
}
2024-11-27 05:20:23 -05:00
return storage
2014-10-31 06:41:07 -04:00
} ,
2012-06-12 11:36:25 -04:00
2014-10-31 06:41:07 -04:00
/ * *
* Deletes the storage from the given tr
*
* @ param $tr storage row
* @ param Function callback callback to call after save
* /
2024-11-27 05:20:23 -05:00
deleteStorageConfig ( $tr ) {
const self = this
const configId = $tr . data ( 'id' )
2014-10-31 06:41:07 -04:00
if ( ! _ . isNumber ( configId ) ) {
// deleting unsaved storage
2024-11-27 05:20:23 -05:00
$tr . remove ( )
return
2014-10-31 06:41:07 -04:00
}
2024-11-27 05:20:23 -05:00
const storage = new this . _storageConfigClass ( configId )
2014-10-31 06:41:07 -04:00
2025-09-03 08:51:57 -04:00
OC . dialogs . confirm (
t ( 'files_external' , 'Are you sure you want to disconnect this external storage?' )
+ ' '
+ t ( 'files_external' , 'It will make the storage unavailable in {instanceName} and will lead to a deletion of these files and folders on any sync client that is currently connected but will not delete any files and folders on the external storage itself.' ,
{
storage : this . mountPoint ,
instanceName : window . OC . theme . name ,
} ,
) ,
t ( 'files_external' , 'Delete storage?' ) ,
function ( confirm ) {
if ( confirm ) {
self . updateStatus ( $tr , StorageConfig . Status . IN _PROGRESS )
storage . destroy ( {
success ( ) {
$tr . remove ( )
} ,
error ( result ) {
const statusMessage = ( result && result . responseJSON ) ? result . responseJSON . message : undefined
self . updateStatus ( $tr , StorageConfig . Status . ERROR , statusMessage )
} ,
} )
}
} ,
)
2014-10-31 06:41:07 -04:00
} ,
/ * *
* Saves the storage from the given tr
*
* @ param $tr storage row
* @ param Function callback callback to call after save
2024-11-27 05:20:23 -05:00
* @ param callback
2015-09-16 11:19:13 -04:00
* @ param concurrentTimer only update if the timer matches this
2014-10-31 06:41:07 -04:00
* /
2024-11-27 05:20:23 -05:00
saveStorageConfig ( $tr , callback , concurrentTimer ) {
const self = this
const storage = this . getStorageConfig ( $tr )
2016-01-19 08:16:11 -05:00
if ( ! storage || ! storage . validate ( ) ) {
2024-11-27 05:20:23 -05:00
return false
2014-10-31 06:41:07 -04:00
}
2024-11-27 05:20:23 -05:00
this . updateStatus ( $tr , StorageConfig . Status . IN _PROGRESS )
2014-10-31 06:41:07 -04:00
storage . save ( {
2024-11-27 05:20:23 -05:00
success ( result ) {
2015-09-16 11:19:13 -04:00
if ( concurrentTimer === undefined
|| $tr . data ( 'save-timer' ) === concurrentTimer
) {
2024-11-27 05:20:23 -05:00
self . updateStatus ( $tr , result . status , result . statusMessage )
$tr . data ( 'id' , result . id )
2015-09-16 11:19:13 -04:00
if ( _ . isFunction ( callback ) ) {
2024-11-27 05:20:23 -05:00
callback ( storage )
2015-09-16 11:19:13 -04:00
}
2014-10-31 06:41:07 -04:00
}
} ,
2024-11-27 05:20:23 -05:00
error ( result ) {
2015-09-16 11:19:13 -04:00
if ( concurrentTimer === undefined
|| $tr . data ( 'save-timer' ) === concurrentTimer
) {
2024-11-27 05:20:23 -05:00
const statusMessage = ( result && result . responseJSON ) ? result . responseJSON . message : undefined
self . updateStatus ( $tr , StorageConfig . Status . ERROR , statusMessage )
2015-09-16 11:19:13 -04:00
}
2024-11-27 05:20:23 -05:00
} ,
} )
2014-10-31 06:41:07 -04:00
} ,
/ * *
* Recheck storage availability
*
* @ param { jQuery } $tr storage row
* @ return { boolean } success
* /
2024-11-27 05:20:23 -05:00
recheckStorageConfig ( $tr ) {
const self = this
const storage = this . getStorageConfig ( $tr )
2014-10-31 06:41:07 -04:00
if ( ! storage . validate ( ) ) {
2024-11-27 05:20:23 -05:00
return false
2014-10-31 06:41:07 -04:00
}
2024-11-27 05:20:23 -05:00
this . updateStatus ( $tr , StorageConfig . Status . IN _PROGRESS )
2014-10-31 06:41:07 -04:00
storage . recheck ( {
2024-11-27 05:20:23 -05:00
success ( result ) {
self . updateStatus ( $tr , result . status , result . statusMessage )
2014-10-31 06:41:07 -04:00
} ,
2024-11-27 05:20:23 -05:00
error ( result ) {
const statusMessage = ( result && result . responseJSON ) ? result . responseJSON . message : undefined
self . updateStatus ( $tr , StorageConfig . Status . ERROR , statusMessage )
} ,
} )
2014-10-31 06:41:07 -04:00
} ,
/ * *
* Update status display
*
* @ param { jQuery } $tr
2022-01-10 08:06:28 -05:00
* @ param { number } status
2015-09-16 11:58:26 -04:00
* @ param { string } message
2014-10-31 06:41:07 -04:00
* /
2024-11-27 05:20:23 -05:00
updateStatus ( $tr , status , message ) {
const $statusSpan = $tr . find ( '.status span' )
2014-10-31 06:41:07 -04:00
switch ( status ) {
2024-11-27 05:20:23 -05:00
case null :
// remove status
$statusSpan . hide ( )
break
case StorageConfig . Status . IN _PROGRESS :
$statusSpan . attr ( 'class' , 'icon-loading-small' )
break
case StorageConfig . Status . SUCCESS :
$statusSpan . attr ( 'class' , 'success icon-checkmark-white' )
break
case StorageConfig . Status . INDETERMINATE :
$statusSpan . attr ( 'class' , 'indeterminate icon-info-white' )
break
default :
$statusSpan . attr ( 'class' , 'error icon-error-white' )
2014-10-31 06:41:07 -04:00
}
2024-07-29 06:13:21 -04:00
if ( status !== null ) {
2024-11-27 05:20:23 -05:00
$statusSpan . show ( )
2024-07-29 06:13:21 -04:00
}
2024-07-29 06:18:12 -04:00
if ( typeof message !== 'string' ) {
2024-11-27 05:20:23 -05:00
message = t ( 'files_external' , 'Click to recheck the configuration' )
2014-10-31 06:41:07 -04:00
}
2024-11-27 05:20:23 -05:00
$statusSpan . attr ( 'title' , message )
2014-10-31 06:41:07 -04:00
} ,
/ * *
* Suggest mount point name that doesn ' t conflict with the existing names in the list
*
* @ param { string } defaultMountPoint default name
* /
2024-11-27 05:20:23 -05:00
_suggestMountPoint ( defaultMountPoint ) {
const $el = this . $el
const pos = defaultMountPoint . indexOf ( '/' )
2012-12-28 15:56:48 -05:00
if ( pos !== - 1 ) {
2024-11-27 05:20:23 -05:00
defaultMountPoint = defaultMountPoint . substring ( 0 , pos )
2012-12-28 15:56:48 -05:00
}
2024-11-27 05:20:23 -05:00
defaultMountPoint = defaultMountPoint . replace ( /\s+/g , '' )
let i = 1
let append = ''
let match = true
2012-08-13 17:09:37 -04:00
while ( match && i < 20 ) {
2024-11-27 05:20:23 -05:00
match = false
2014-10-31 06:41:07 -04:00
$el . find ( 'tbody td.mountPoint input' ) . each ( function ( index , mountPoint ) {
2024-11-27 05:20:23 -05:00
if ( $ ( mountPoint ) . val ( ) === defaultMountPoint + append ) {
match = true
return false
2012-08-13 17:09:37 -04:00
}
2024-11-27 05:20:23 -05:00
} )
2012-08-13 17:09:37 -04:00
if ( match ) {
2024-11-27 05:20:23 -05:00
append = i
i ++
2012-08-13 17:09:37 -04:00
} else {
2024-11-27 05:20:23 -05:00
break
2012-08-13 17:09:37 -04:00
}
}
2024-11-27 05:20:23 -05:00
return defaultMountPoint + append
2015-03-16 09:07:53 -04:00
} ,
2015-09-13 18:23:42 -04:00
2015-03-16 09:07:53 -04:00
/ * *
* Toggles the mount options dropdown
*
2024-11-27 05:20:23 -05:00
* @ param { object } $tr configuration row
2015-09-13 18:23:42 -04:00
* /
2024-11-27 05:20:23 -05:00
_showMountOptionsDropdown ( $tr ) {
const self = this
const storage = this . getStorageConfig ( $tr )
const $toggle = $tr . find ( '.mountOptionsToggle' )
const dropDown = new MountOptionsDropdown ( )
const visibleOptions = [
2016-05-02 11:34:24 -04:00
'previews' ,
'filesystem_check_changes' ,
'enable_sharing' ,
2018-02-27 08:06:14 -05:00
'encoding_compatibility' ,
2018-08-01 03:19:52 -04:00
'readonly' ,
2024-11-27 05:20:23 -05:00
'delete' ,
]
2015-03-31 10:25:33 -04:00
if ( this . _encryptionEnabled ) {
2024-11-27 05:20:23 -05:00
visibleOptions . push ( 'encrypt' )
2015-03-31 10:25:33 -04:00
}
2024-11-27 05:20:23 -05:00
dropDown . show ( $toggle , storage . mountOptions || [ ] , visibleOptions )
2015-03-16 09:07:53 -04:00
$ ( 'body' ) . on ( 'mouseup.mountOptionsDropdown' , function ( event ) {
2024-11-27 05:20:23 -05:00
const $target = $ ( event . target )
2018-02-22 06:13:35 -05:00
if ( $target . closest ( '.popovermenu' ) . length ) {
2024-11-27 05:20:23 -05:00
return
2015-03-16 09:07:53 -04:00
}
2024-11-27 05:20:23 -05:00
dropDown . hide ( )
} )
2015-03-16 09:07:53 -04:00
dropDown . $el . on ( 'hide' , function ( ) {
2024-11-27 05:20:23 -05:00
const mountOptions = dropDown . getOptions ( )
$ ( 'body' ) . off ( 'mouseup.mountOptionsDropdown' )
$tr . find ( 'input.mountOptions' ) . val ( JSON . stringify ( mountOptions ) )
$tr . find ( 'td.mountOptionsToggle>.icon-more' ) . attr ( 'aria-expanded' , 'false' )
self . saveStorageConfig ( $tr )
} )
} ,
} , OC . Backbone . Events )
2012-08-13 17:09:37 -04:00
2020-07-20 06:30:35 -04:00
window . addEventListener ( 'DOMContentLoaded' , function ( ) {
2024-11-27 05:20:23 -05:00
const enabled = $ ( '#files_external' ) . attr ( 'data-encryption-enabled' )
const canCreateLocal = $ ( '#files_external' ) . attr ( 'data-can-create-local' )
const encryptionEnabled = ( enabled === 'true' )
const mountConfigListView = new MountConfigListView ( $ ( '#externalStorage' ) , {
encryptionEnabled ,
canCreateLocal : ( canCreateLocal === 'true' ) ,
} )
mountConfigListView . loadStorages ( )
2012-12-28 17:38:24 -05:00
2014-10-31 06:41:07 -04:00
// TODO: move this into its own View class
2024-11-27 05:20:23 -05:00
const $allowUserMounting = $ ( '#allowUserMounting' )
2014-10-13 12:40:57 -04:00
$allowUserMounting . bind ( 'change' , function ( ) {
2024-11-27 05:20:23 -05:00
OC . msg . startSaving ( '#userMountingMsg' )
2012-06-08 11:42:00 -04:00
if ( this . checked ) {
2024-11-27 05:20:23 -05:00
OCP . AppConfig . setValue ( 'files_external' , 'allow_user_mounting' , 'yes' )
$ ( 'input[name="allowUserMountingBackends\\[\\]"]' ) . prop ( 'checked' , true )
$ ( '#userMountingBackends' ) . removeClass ( 'hidden' )
$ ( 'input[name="allowUserMountingBackends\\[\\]"]' ) . eq ( 0 ) . trigger ( 'change' )
2012-06-08 11:42:00 -04:00
} else {
2024-11-27 05:20:23 -05:00
OCP . AppConfig . setValue ( 'files_external' , 'allow_user_mounting' , 'no' )
$ ( '#userMountingBackends' ) . addClass ( 'hidden' )
2012-06-08 11:42:00 -04:00
}
2024-11-27 05:20:23 -05:00
OC . msg . finishedSaving ( '#userMountingMsg' , { status : 'success' , data : { message : t ( 'files_external' , 'Saved' ) } } )
} )
2012-05-24 11:06:03 -04:00
2014-02-18 10:36:02 -05:00
$ ( 'input[name="allowUserMountingBackends\\[\\]"]' ) . bind ( 'change' , function ( ) {
2024-11-27 05:20:23 -05:00
OC . msg . startSaving ( '#userMountingMsg' )
2015-09-17 12:00:15 -04:00
2024-11-27 05:20:23 -05:00
let userMountingBackends = $ ( 'input[name="allowUserMountingBackends\\[\\]"]:checked' ) . map ( function ( ) {
return $ ( this ) . val ( )
} ) . get ( )
const deprecatedBackends = $ ( 'input[name="allowUserMountingBackends\\[\\]"][data-deprecate-to]' ) . map ( function ( ) {
2015-09-17 12:00:15 -04:00
if ( $ . inArray ( $ ( this ) . data ( 'deprecate-to' ) , userMountingBackends ) !== - 1 ) {
2024-11-27 05:20:23 -05:00
return $ ( this ) . val ( )
2015-09-17 12:00:15 -04:00
}
2024-11-27 05:20:23 -05:00
return null
} ) . get ( )
userMountingBackends = userMountingBackends . concat ( deprecatedBackends )
2015-09-17 12:00:15 -04:00
2024-11-27 05:20:23 -05:00
OCP . AppConfig . setValue ( 'files_external' , 'user_mounting_backends' , userMountingBackends . join ( ) )
OC . msg . finishedSaving ( '#userMountingMsg' , { status : 'success' , data : { message : t ( 'files_external' , 'Saved' ) } } )
2014-06-13 12:14:41 -04:00
// disable allowUserMounting
2024-11-27 05:20:23 -05:00
if ( userMountingBackends . length === 0 ) {
$allowUserMounting . prop ( 'checked' , false )
$allowUserMounting . trigger ( 'change' )
2014-06-13 12:14:41 -04:00
}
2024-11-27 11:01:25 -05:00
} )
2024-11-27 05:20:23 -05:00
$ ( '#global_credentials' ) . on ( 'submit' , async function ( event ) {
event . preventDefault ( )
const $form = $ ( this )
const $submit = $form . find ( '[type=submit]' )
$submit . val ( t ( 'files_external' , 'Saving …' ) )
2014-10-31 06:41:07 -04:00
2024-11-27 05:20:23 -05:00
const uid = $form . find ( '[name=uid]' ) . val ( )
const user = $form . find ( '[name=username]' ) . val ( )
const password = $form . find ( '[name=password]' ) . val ( )
2024-10-30 01:59:52 -04:00
2025-03-07 08:22:25 -05:00
try {
await axios . request ( {
method : 'POST' ,
data : {
uid ,
user ,
password ,
} ,
url : generateUrl ( 'apps/files_external/globalcredentials' ) ,
confirmPassword : PwdConfirmationMode . Strict ,
} )
$submit . val ( t ( 'files_external' , 'Saved' ) )
setTimeout ( function ( ) {
$submit . val ( t ( 'files_external' , 'Save' ) )
} , 2500 )
} catch ( error ) {
2024-11-27 05:20:23 -05:00
$submit . val ( t ( 'files_external' , 'Save' ) )
2025-03-07 08:22:25 -05:00
if ( isAxiosError ( error ) ) {
const message = error . response ? . data ? . message || t ( 'files_external' , 'Failed to save global credentials' )
showError ( t ( 'files_external' , 'Failed to save global credentials: {message}' , { message } ) )
}
}
2024-10-30 01:59:52 -04:00
2024-11-27 05:20:23 -05:00
return false
} )
2016-06-21 07:55:07 -04:00
2014-10-31 06:41:07 -04:00
// global instance
2024-11-27 05:20:23 -05:00
OCA . Files _External . Settings . mountConfig = mountConfigListView
2014-10-31 06:41:07 -04:00
/ * *
* Legacy
*
* @ namespace
2018-10-19 11:35:13 -04:00
* @ deprecated use OCA . Files _External . Settings . mountConfig instead
2014-10-31 06:41:07 -04:00
* /
OC . MountConfig = {
2024-11-27 05:20:23 -05:00
saveStorage : _ . bind ( mountConfigListView . saveStorageConfig , mountConfigListView ) ,
}
} )
2013-10-10 05:10:32 -04:00
2014-10-31 06:41:07 -04:00
// export
2024-11-27 05:20:23 -05:00
OCA . Files _External = OCA . Files _External || { }
2014-10-31 06:41:07 -04:00
/ * *
* @ namespace
* /
2024-11-27 05:20:23 -05:00
OCA . Files _External . Settings = OCA . Files _External . Settings || { }
2014-10-31 06:41:07 -04:00
2024-11-27 05:20:23 -05:00
OCA . Files _External . Settings . GlobalStorageConfig = GlobalStorageConfig
OCA . Files _External . Settings . UserStorageConfig = UserStorageConfig
OCA . Files _External . Settings . MountConfigListView = MountConfigListView