Compare commits

...

140 Commits

Author SHA1 Message Date
Frank Karlitschek 508fd15975 7.0.0 2014-07-21 18:55:21 -04:00
Joas Schilling e482ba60bc Correctly use groups parameter only when its not empty
Fix #9745

Backport of c84c1f5 from master
2014-07-21 20:45:27 +02:00
Thomas Müller bad2d4d408 perm -> permissions 2014-07-21 17:14:21 +02:00
Joas Schilling a77044da9e Do not force isAdmin as true and so the list is filtered correctly 2014-07-21 14:32:20 +02:00
Joas Schilling 9c6e87849b Fix username for subadmins and only send subadmin groups
Fix #9748
2014-07-21 14:32:20 +02:00
Vincent Petry f80f8d9cc4 Merge pull request #9723 from dupondje/stable7
Small translation fix. Fixes: #9719
2014-07-21 10:49:03 +02:00
Thomas Müller 00c0495703 Merge pull request #9749 from owncloud/backport-9672-stable7
Backport 9672 stable7
2014-07-21 00:36:23 +02:00
Vincent Petry e7f7ac38c9 Added test of OCS privatedata to trigger key duplication 2014-07-20 23:33:11 +02:00
Andreas Fischer 6094da6c76 Document why we have to check with defined() first. 2014-07-20 23:33:11 +02:00
Andreas Fischer 22c957d475 Make MySQL return "number of found rows" instead of number of "affected rows". 2014-07-20 23:33:11 +02:00
Morris Jobke 06158966f1 Merge pull request #9742 from owncloud/fix-repair-innodb-9737-stable7
check if $tables is an array
2014-07-20 21:36:52 +02:00
Arthur Schiwon 47d2e963be $.unique works only for DOM elements 2014-07-20 16:09:01 +02:00
Andreas Fischer 7403476489 Pass existing Net_SFTP object into Net_SFTP_Stream. 2014-07-20 15:16:31 +02:00
Thomas Müller 981bd7da2a check if $tables is an array 2014-07-19 20:24:34 +02:00
Lukas Reschke bab5de8e8f Merge pull request #9706 from owncloud/keep_session_in_sync_stable7
keep session in sync
2014-07-19 10:37:14 +02:00
Arthur Schiwon acf80ba7cc Backport #9576
warn and continue gracefully if bcmath is not installed

make tests deal with missing bcmath
2014-07-18 19:17:19 +02:00
Jean-Louis Dupond abe2c8ce76 Small translation fix. Fixes: #9719 2014-07-18 14:41:37 +02:00
Andreas Fischer 0b8de8087b login() must be called after getServerPublicHostKey(). 2014-07-18 13:35:23 +02:00
Jörn Friedrich Dreyer 34cb09b777 fix user session tests by making them update \OC:: as well 2014-07-18 10:46:17 +02:00
Frank Karlitschek ef202509f3 update upstore api url 2014-07-17 21:52:14 -04:00
Frank Karlitschek cce2cb578f update the appstore api url 2014-07-17 21:51:32 -04:00
Frank Karlitschek 8d851435b6 7.0.0 RC3 2014-07-17 21:18:17 -04:00
Thomas Müller b1575eda3a Merge pull request #9690 from owncloud/kill-too-long-index-stable7
Revert add-share-index
2014-07-17 20:53:59 +02:00
Thomas Müller fc6d1177b7 Revert "add share index"
This reverts commit e19b3a8794.
2014-07-17 17:19:43 +02:00
Thomas Müller a66ee26187 kill unused require of MapperTestUtility.php 2014-07-17 17:18:42 +02:00
Vincent Petry e2f2313eb5 Fixed JS and CSS issues in users page
- Renamed "delete" to "deleteEntry" to make IE8 happy.
- Added missing "svg" class for the "+" button
- Added height to "+" button but was unable to properly align it
2014-07-17 16:46:41 +02:00
Thomas Müller 7886b900f1 fixing namespace of MapperTestUtility
and rename file to be lowercase
2014-07-17 15:53:16 +02:00
Vincent Petry b020e6b308 Merge pull request #9674 from owncloud/stable7-9647
[stable7] Add unit test for multi-user configuration loading
2014-07-17 13:57:38 +02:00
Bjoern Schiessle 4de6896028 check that the file proxies are enabled after each test 2014-07-17 13:17:11 +02:00
Bjoern Schiessle f5aa292587 update keys recursively if a folder was moved 2014-07-17 13:17:06 +02:00
Jörn Friedrich Dreyer 0cde504e80 keep session in sync 2014-07-17 13:03:56 +02:00
Thomas Müller 7e3a4b59a2 Merge pull request #9695 from owncloud/revert-backport-9550-stable7
Revert backport 9550 stable7
2014-07-17 11:02:39 +02:00
Thomas Müller 36039ca0c3 Revert "files: storage: rename should check parent directories of old and new files"
This reverts commit a163b185ab.
2014-07-17 09:57:08 +02:00
Thomas Müller 8af6b723bf Revert "Fix renaming files in the root folder of a MappedLocal storage"
This reverts commit 0895324553.
2014-07-17 09:56:52 +02:00
Robin Appelman 0895324553 Fix renaming files in the root folder of a MappedLocal storage 2014-07-17 09:19:20 +02:00
Morris Jobke e0a69d4c0a fix CSS coding style 2014-07-16 18:05:35 +02:00
Stephane V 198a30d964 Fix #9590. Fix #9612.
For external storage with lots of parameter settings (>4), wrap the content of the cell to let a full view of the parameters.
The rows of the table are now always visible until the end (the trash icon is accessible).
(Note : A strange 3px margin forces me to add a class on the row added by javascript, to be able to align them with the rows rendered by the server.)
2014-07-16 18:05:35 +02:00
Tigran Mkrtchyan a163b185ab files: storage: rename should check parent directories of old and new files
as described by POSIX.1-2008
(see http://pubs.opengroup.org/onlinepubs/9699919799/functions/rename.html)

Signed-off-by: Tigran Mkrtchyan <tigran.mkrtchyan@desy.de>
2014-07-16 16:09:05 +02:00
Robin McCorkell 4fa39283f6 Add unit test for multi-user configuration loading 2014-07-16 14:54:00 +01:00
Bjoern Schiessle ad249155ec introduce some encryption exceptions and catch additional error cases 2014-07-16 15:32:39 +02:00
Bjoern Schiessle ec30cc4f21 make sure that the crypt library is loaded 2014-07-16 15:32:30 +02:00
Vincent Petry ca34fb9ea0 Correctly return the owner display name for children of shares 2014-07-16 14:26:16 +02:00
Vincent Petry f40554fb2e Merge pull request #9584 from anexia-it/bug-9583
Bugfix for issue #9583
2014-07-16 11:20:35 +02:00
Bjoern Schiessle cca04f3514 make sure that 'OC_Theme' exists before checking if the method exists 2014-07-16 10:13:51 +02:00
Jörn Friedrich Dreyer 8ebe6ce7bb fix logger implementation and test 2014-07-15 20:37:07 +02:00
Jörn Friedrich Dreyer 292ce3f484 mkae getRelativePath of file search results overwriteable in subclasses 2014-07-15 20:33:59 +02:00
Jörn Friedrich Dreyer eaa5c530de return relative path 2014-07-15 20:33:50 +02:00
Jörn Friedrich Dreyer ebce1e1c41 use fileinfo object in search results 2014-07-15 20:33:37 +02:00
DeLtAfOx d3b6a6333e backport of #9628
Userlist: async load doesnt fill checked group/subadmin array

Strinct comparsion fix
2014-07-15 19:11:37 +02:00
Lukas Reschke c5c600bd7b Update info.xml 2014-07-15 17:28:43 +02:00
Vincent Petry 078637130e - Added test to trigger index id error
- re-enable encryption migration tests
- sqlite requires reconnect after schema changes

Backport of 10a2955 from master
2014-07-15 17:04:50 +02:00
libasys 3113e99c90 Update share.php
Added empty for error:
```
Undefined index: file_target at /var/www/owncloud/lib/private/share/share.php#1911
```
2014-07-15 16:56:28 +02:00
Bjoern Schiessle 9fa495cf70 throw exception if file is to large for trash bin 2014-07-15 13:57:01 +02:00
Philippe Jung 5a36841144 Backport of #9570
[Issue #9559] identifiers uid=xxx must be considered as user DN and not as owncloud users

File written by blizzz
2014-07-15 11:46:44 +02:00
Stephan Peijnik b51d54a84c Updated code to reflect changes introduced to \OC\Group\MetaData.
Now that fetchGroups() does not exist anymore and getGroups() is called
directory, the 'groups' property does not exist anymore.
Instead, we now generate that array on the fly and return it from getGroups.

Signed-off-by: Stephan Peijnik <speijnik@anexia-it.com>
2014-07-15 11:15:38 +02:00
Stephan Peijnik a4a0ebf9db Use is_null() instead of empty() when checking the return value of GroupManager::get().
Additionally, $grp was renamed to $group inside
 \OC\Group\MetaData::fetchGroups().

Signed-off-by: Stephan Peijnik <speijnik@anexia-it.com>
2014-07-15 10:56:20 +02:00
Stephan Peijnik a58f85d3ee Do not remove spaces from group IDs in \OC\Group\MetaData::generateGroupMetaData.
Change suggested by blizz in PR #9584.

Signed-off-by: Stephan Peijnik <speijnik@anexia-it.com>
2014-07-15 10:56:10 +02:00
Stephan Peijnik 3de1d2cfff Renamed $grp to $group in foreach loop.
Signed-off-by: Stephan Peijnik <speijnik@anexia-it.com>
2014-07-15 10:55:43 +02:00
Stephan Peijnik 75d45c4e49 Fix indentation.
Signed-off-by: Stephan Peijnik <speijnik@anexia-it.com>
2014-07-15 10:55:43 +02:00
Stephan Peijnik 0acb76c97f Fixes #9583
lib/private/group/metadata.php: For subadmins also return an array of groups, indexed by their GIDs.
settings/users.php: Convert array of arrays to array of GIDs before calling into OC_Group::displayNamesInGroups.

Signed-off-by: Stephan Peijnik <speijnik@anexia-it.com>
2014-07-15 10:55:32 +02:00
Thomas Müller 8e8c6c9f72 reduce share action text to the user name only 2014-07-15 09:16:11 +02:00
Arthur Schiwon 334ace9080 Backport of #9562
remove dead code

do not filter groups. but update the user count according to the filter

improve phpdoc

improve metadata runtime cache

add metadata tests

fixing PHPDoc
2014-07-15 08:31:43 +02:00
Robin Appelman 8ec00dcb66 Use the correct path when building the FileInfo for the search result 2014-07-14 21:33:07 +02:00
Thomas Müller b7d717e47c append file extension to the temporary file which contains the downloaded archive - in case of zip files fileinfo doesn't seem to return anything reliable 2014-07-14 17:06:36 +02:00
Robin Appelman 9d3336002b Remove deleted versions from the cache 2014-07-14 15:32:33 +02:00
Robin Appelman c5278a421a Dont delete versions as local files 2014-07-14 15:32:19 +02:00
Robin Appelman 36360a6e8a Expose the user manager in the public server container 2014-07-14 15:14:36 +02:00
Robin Appelman 169b328d41 Add public interfaces for User, UserManager and UserSession 2014-07-14 15:14:29 +02:00
Daniel Hansson 1a01debe68 Fix for #9422 2014-07-14 12:59:07 +02:00
Vincent Petry 1cc8be0701 Added mountType attribute and adapted Delete action text
Added mountType attribute for files/folder to indicated whether they are
regular, external or shared.

The client side then adapts the "Delete" action hint text based on this
information.

Only the mount roots must have the delete icon hint adapted.
To make this detectable on the client side, the mountType can now
be null, "shared", "shared-root", "external" or "external-root".

This also gives room to icon customization on the client side.
2014-07-14 11:54:05 +02:00
Andreas Fischer bae4579d60 Add short description explaining how SFTP ext storage class works. 2014-07-14 11:45:35 +02:00
Andreas Fischer fae2a00922 The file providing the sftp:// scheme needs to be included manually. 2014-07-14 11:45:28 +02:00
Georg Ehrke 9ebb037fc2 improve check for duplicate apps 2014-07-14 10:00:38 +02:00
Georg Ehrke 5bb42bedec add proper version comparision in OC_Installer::isUpdateAvailable 2014-07-14 10:00:33 +02:00
Frank Karlitschek e3d50804f7 7RC2 2014-07-13 22:45:11 -04:00
Vincent Petry 14ea0ea3bc Propagate file action changes to the file lists
Whenever an app needs to register an event late, it does that on the
original file actions object.

Since the file actions that the file list work on is a merged list, not
the original one, the registration event needs to be propagated there as
well.
2014-07-10 16:15:46 +02:00
Thomas Müller 66130ad336 fixing JS syntax errors
remove the group in case the last user has removed from that group

cleanup

use .filterAttr()
2014-07-10 16:13:01 +02:00
Arthur Schiwon a310415b09 increment group counters when a user is created
decrease user count in affected groups after user delete

increase/decrease everyone count on user creation/deletion

avoid global selector
2014-07-10 16:12:53 +02:00
Thomas Müller 10bac56551 adding 'groups' entry to remote apps 2014-07-10 15:47:29 +02:00
Thomas Müller 32c6afba90 fixing typos 2014-07-10 15:47:29 +02:00
Robin Appelman 31d8bce383 debounce the search function 2014-07-10 15:32:17 +02:00
Morris Jobke 2fb022fbc3 fix translations 2014-07-09 16:19:32 +02:00
Volkan Gezer 5911188211 fix apostrophe fixes #9486 2014-07-09 16:19:32 +02:00
libasys 9ccadcfe80 BugFix missing $item on 'file_target' Line 1911
I think this should be right!

Backport of e70a7af6da from master
2014-07-09 16:05:47 +02:00
Arthur Schiwon 9ee46bbe91 test class is already in preferences.php
Conflicts:
	tests/lib/preferences-singleton.php
2014-07-09 13:14:34 +02:00
Arthur Schiwon bc1ff4744b support for AD primary groups
support for primary groups

actually the problem is only known on AD, it is only needed to take care of their attributes

adjust to ADs special behaviour

this change was not intended

cache the SID value so it is not requested over and over again

theres only one, use singular

we are access

add tests for new Access methods

add tests for new Group methods

address scrutinizer findings, mostly doc

call ldap_explode_dn from ldap wrapper, enables tests without php5-ldap

PHP Doc

yo dawg, i heard you like backslashes … php doc fix

PHPDoc updated and typos fixed while reviewing
2014-07-08 21:34:38 +02:00
Vincent Petry 6043a90a71 Fix update cleanup to only affect file and folders
Fix bug in the SQL query that cleans up stray shares for removed
files/folders, which is now correctly limited to that item type instead
of also removing all other share types.

Backport of f4f52cf from master
2014-07-08 18:10:03 +02:00
Thomas Müller 0e62a7dc59 Upload abortion is now detected within the OC_Connector_Sabre_File::put()
OC_Connector_Sabre_AbortedUploadDetectionPlugin is pointless

Adding unit test testUploadAbort()
2014-07-08 17:40:08 +02:00
Thomas Müller 7a6b76f96e Adding new interface \OCP\Activity\IExtentsion
Adding method getNotificationTypes()
Adding method filterNotificationTypes()
Adding method getDefaultTypes()
Adding method translate() and getTypeIcon()
Adding method getGroupParameter()
Adding method getNavigation()
Adding method getNavigation()
Adding method isFilterValid() and getQueryForFilter()
Adding unit tests for \OC\ActivityManager
2014-07-08 16:38:18 +02:00
blizzz a78293dd3f Merge pull request #9500 from owncloud/fix9475
Avoid unnecessary writing to the DB when prefrences are not changed in fact
2014-07-08 16:30:09 +02:00
Robin Appelman 1ce9ba1ebc use case insensitive LIKE when searching for files in mysql 2014-07-08 15:49:05 +02:00
Robin Appelman 2f15ae988f Add repair step to set MySQL collation to utf8_bin
Set default collation of mysql connection to utf8_bin
Set utf_bin as default collation for new tables
2014-07-08 15:14:29 +02:00
Christopher T. Johnson c02e95ff40 Fix Signiture Does Not Match when mounting Amazon S3 external storage
For some reason the aws-sdk-php package does not caclulate the
signiture correctly when accessing an object in a bucket with a name of
'.'.

When we are at the top of a S3 bucket there is a need(?) to have a directory
name.  Per standard Unix the name picked was '.' (dot or period).  This
choice exercises the aws-sdk bug.

This fix is to add a field to the method to store the name to use instead of
'.' which at this point is hard coded to '<root>'.  We also add a private
function 'cleanKey()' which will test for the '.' name and replace it with
the variable.  Finally all calls to manipulate objects where the path is
not obviously not '.' are processed through cleanKey().

An example where we don't process through clean key would be
	'Key' => $path.'/',

Use correct relationship operator

Per feed back use === instead of ==

use '/' instead of '<root>'
2014-07-08 14:26:34 +02:00
Arthur Schiwon 03c13021db also appconfig shall not write to database if the value is unchanged 2014-07-08 12:30:38 +02:00
Morris Jobke 01c0601d39 specify CSS rule for warning & update fieldsets - fixes #9491 2014-07-08 09:45:57 +02:00
Stephane V 8bd5c6e04d Fixes #9497 2014-07-08 01:19:24 +02:00
Arthur Schiwon 76b310de9d add test cases. also split test classes in a file each, looks like only the first class is being executed 2014-07-08 00:20:46 +02:00
Arthur Schiwon ee2886331e do not write to database when the value is the same 2014-07-08 00:19:58 +02:00
Arthur Schiwon ed1c918d9e don't trigger update from checkPassword, it is already called by userExists, this is enough. 2014-07-08 00:19:17 +02:00
Robin Appelman 18f5f85160 When changing the mountpoint of an external storage, ensure the old one is removed 2014-07-07 23:25:26 +02:00
Vincent Petry 19dedf3d61 Do not show recipient for link shares in file list
In the "Shared with link" section, the share_with field can contain a
value when a password was set.

This fix prevents this value to be shown as it is not intended for the
end user.
2014-07-07 20:04:03 +02:00
Georg Ehrke efadfedbaa add ocsids to info.xml 2014-07-07 20:00:08 +02:00
Vincent Petry 60e3195600 Improved external share dialog
Changed title, label and buttons.
Removed icon.
2014-07-07 19:53:19 +02:00
Vincent Petry db72e90504 Fixed dialogs styling, reversed buttons
Default dialog button is now on the right, other one on the left.
2014-07-07 19:52:53 +02:00
Joas Schilling cf982e9818 Add comment to overwrite* configs about CLI/cron problems 2014-07-07 19:40:28 +02:00
Joas Schilling dccab5d20f Only calculate the WEBROOT from scriptName if it contains $SUBURI
If not we are most likely in CLI mode. However to be able to still
generate valid URLs, we need to use the overwrite webroot instead.

Fix #9490
2014-07-07 19:39:58 +02:00
Joas Schilling f5e4ebf2ba Only overwrite serverHost when in unit tests
The intention of the original commit 12f7cb8767
was to fix tests. But cron should always return a valid host not localhost.
2014-07-07 19:39:20 +02:00
Vincent Petry 2bfdd02c2a Fixed shared list sorting
Use Array.sort instead of underscore's sortBy() as they don't use the
same method/function signature.
2014-07-07 19:33:22 +02:00
Thomas Müller 8b97073e13 MySQL: adding repair step to convert MyIsam tables to InnoDB 2014-07-07 15:51:52 +02:00
Georg Ehrke d2fd78a0c9 fix phpDocBlock for OC_App::getAppInfo 2014-07-07 15:03:38 +02:00
Georg Ehrke 59fd9d7517 better validation: cadd extra check if appinfo/info.xml exists 2014-07-07 15:03:33 +02:00
Stephane V 5344d9beab Bug 9147 owncloud/core
Added class="date" in the latest column of the log table to get everything on one line (in the ajax request).
2014-07-07 13:58:01 +02:00
Morris Jobke 127ce3c5d9 fix loading spinner on ctrl click a app entry- fixes #9063 2014-07-07 12:19:07 +02:00
Georg Ehrke 04604dae0d improvements for uninstall button 2014-07-06 23:57:27 +02:00
Volkan Gezer 0fe1f25a9e Merge pull request #9466 from owncloud/design-fix-fieldset-legend
fix fieldset look, fix #8158
2014-07-06 16:06:25 +02:00
Morris Jobke a5d34b435f also make strengthify transparent on setup submit - fixes #9436 2014-07-05 10:31:27 +02:00
Morris Jobke 67cf1d61e1 fix size of breadcrumb separator 2014-07-04 21:37:54 +02:00
Bjoern Schiessle 5549148039 add owner as parameter for delShareKey 2014-07-04 19:00:28 +02:00
Bjoern Schiessle 0319ee3894 make sure that the umount hook always contains the path relative to data/user/files 2014-07-04 19:00:20 +02:00
Bjoern Schiessle 2bd3aa6b21 if a folder gets deleted we unshare all shared files/folders below 2014-07-04 19:00:05 +02:00
Jörn Friedrich Dreyer 36d2aab945 deprecate OC_Search_Provider in favor of \OCP\Search\Provider 2014-07-04 18:29:26 +02:00
Jörn Friedrich Dreyer 0a4e95cc07 fix '' to '/' when determining parent for search result 2014-07-04 18:29:16 +02:00
Robin Appelman b429a71660 Add machine readable error messages to OC\JSON
Reload the files app in case of authentication errors, expired tokens or disabled app

Reloading will triger the full server side handeling of those errors

formatting

fix missing semicolon + some jshint warnings
2014-07-04 16:39:45 +02:00
Jan-Christoph Borchardt b33c61798c show loading feedback also when clicking 'Apps' entry in app list 2014-07-04 16:37:51 +02:00
Thomas Müller 11a2c0249d update snap.js to v2.0.0-rc1 2014-07-04 16:34:19 +02:00
Vincent Petry 86545a90d0 Fix reload call for all subclasses
All subclasses must also properly return the ajax call object.
2014-07-04 16:13:52 +02:00
Jörn Friedrich Dreyer 71261decf1 make search case insensitive on postgres and oracle 2014-07-04 15:53:10 +02:00
Jörn Friedrich Dreyer f390ae53ba introduce and use getCurrentConnection() 2014-07-04 15:26:30 +02:00
Jörn Friedrich Dreyer 195cf273f8 reset collection to 'root' after adding a route to the api 2014-07-04 15:26:21 +02:00
Jörn Friedrich Dreyer 3a1c7182e6 change order of registering api and capabilities to fix file version previews 2014-07-04 15:26:11 +02:00
Robin Appelman c272338897 set localhost as default database host for installation 2014-07-04 15:21:26 +02:00
Morris Jobke 74edbd5df0 fix cursor for other elements in top right corner 2014-07-04 14:58:19 +02:00
Jan-Christoph Borchardt c28fb2de4e fix 'remember' label being slightly off in log in 2014-07-04 14:58:14 +02:00
Jan-Christoph Borchardt b343b18cd9 fix icons being slightly off in log in 2014-07-04 14:58:09 +02:00
Jan-Christoph Borchardt 2224d1c0d3 fix user menu name and image not showing clicky mouse pointer 2014-07-04 14:58:05 +02:00
Vincent Petry b42215a3c9 Fix FileActions merging override
When merging FileActions, the register() functio needs to correctly
override the previously merged action handler without affecting the
original one.
2014-07-04 14:49:15 +02:00
Morris Jobke 2115a9bf1a IE8 fixes 2014-07-04 14:14:12 +02:00
Jan-Christoph Borchardt 843ad18bf3 use icon-confirm instead of text for accepting remote share, works better with translations 2014-07-04 14:14:07 +02:00
Vincent Petry ed4ba00b77 Return and use isPreviewAvailable for share previews
Since the mime type is known, now isPreviewAvailable is returned as well
and used by the JS side to properly render mime icon and previews.
2014-07-04 14:04:53 +02:00
Frank Karlitschek 4a7aaa8914 7RC1 2014-07-03 20:30:45 -04:00
318 changed files with 4900 additions and 1797 deletions
-1
View File
@@ -53,7 +53,6 @@ $server->subscribeEvent('beforeMethod', function () use ($server, $objectTree) {
$rootDir = new OC_Connector_Sabre_Directory($view, $rootInfo);
$objectTree->init($rootDir, $view, $mountManager);
$server->addPlugin(new OC_Connector_Sabre_AbortedUploadDetectionPlugin($view));
$server->addPlugin(new OC_Connector_Sabre_QuotaPlugin($view));
}, 30); // priority 30: after auth (10) and acl(20), before lock(50) and handling the request
+31 -3
View File
@@ -32,9 +32,11 @@
// regular actions
fileActions.merge(OCA.Files.fileActions);
// in case apps would decide to register file actions later,
// replace the global object with this one
OCA.Files.fileActions = fileActions;
this._onActionsUpdated = _.bind(this._onActionsUpdated, this);
OCA.Files.fileActions.on('setDefault.app-files', this._onActionsUpdated);
OCA.Files.fileActions.on('registerAction.app-files', this._onActionsUpdated);
window.FileActions.on('setDefault.app-files', this._onActionsUpdated);
window.FileActions.on('registerAction.app-files', this._onActionsUpdated);
this.files = OCA.Files.Files;
@@ -59,6 +61,32 @@
this._onPopState(OC.Util.History.parseUrlQuery());
},
/**
* Destroy the app
*/
destroy: function() {
this.navigation = null;
this.fileList.destroy();
this.fileList = null;
this.files = null;
OCA.Files.fileActions.off('setDefault.app-files', this._onActionsUpdated);
OCA.Files.fileActions.off('registerAction.app-files', this._onActionsUpdated);
window.FileActions.off('setDefault.app-files', this._onActionsUpdated);
window.FileActions.off('registerAction.app-files', this._onActionsUpdated);
},
_onActionsUpdated: function(ev, newAction) {
// forward new action to the file list
if (ev.action) {
this.fileList.fileActions.registerAction(ev.action);
} else if (ev.defaultAction) {
this.fileList.fileActions.setDefault(
ev.defaultAction.mime,
ev.defaultAction.name
);
}
},
/**
* Returns the container of the currently visible app.
*
+71 -38
View File
@@ -23,48 +23,52 @@
icons: {},
currentFile: null,
/**
* Dummy jquery element, for events
*/
$el: null,
/**
* List of handlers to be notified whenever a register() or
* setDefault() was called.
*/
_updateListeners: [],
_updateListeners: {},
initialize: function() {
this.clear();
// abusing jquery for events until we get a real event lib
this.$el = $('<div class="dummy-fileactions hidden"></div>');
$('body').append(this.$el);
},
/**
* Adds an update listener to be notified whenever register()
* or setDefault() has been called.
* Adds an event handler
*
* @param {String} eventName event name
* @param Function callback
*/
addUpdateListener: function(callback) {
if (!_.isFunction(callback)) {
throw 'Argument passed to FileActions.addUpdateListener must be a function';
}
this._updateListeners.push(callback);
on: function(eventName, callback) {
this.$el.on(eventName, callback);
},
/**
* Removes an update listener.
* Removes an event handler
*
* @param {String} eventName event name
* @param Function callback
*/
removeUpdateListener: function(callback) {
if (!_.isFunction(callback)) {
throw 'Argument passed to FileActions.removeUpdateListener must be a function';
}
this._updateListeners = _.without(this._updateListeners, callback);
off: function(eventName, callback) {
this.$el.off(eventName, callback);
},
/**
* Notifies the registered update listeners
* Notifies the event handlers
*
* @param {String} eventName event name
* @param {Object} data data
*/
_notifyUpdateListeners: function() {
for (var i = 0; i < this._updateListeners.length; i++) {
this._updateListeners[i](this);
}
_notifyUpdateListeners: function(eventName, data) {
this.$el.trigger(new $.Event(eventName, data));
},
/**
@@ -87,21 +91,44 @@
this.defaults = _.extend(this.defaults, fileActions.defaults);
this.icons = _.extend(this.icons, fileActions.icons);
},
register: function (mime, name, permissions, icon, action, displayName) {
/**
* @deprecated use #registerAction() instead
*/
register: function(mime, name, permissions, icon, action, displayName) {
return this.registerAction({
name: name,
mime: mime,
permissions: permissions,
icon: icon,
actionHandler: action,
displayName: displayName
});
},
/**
* Register action
*
* @param {Object} action action object
* @param {String} action.name identifier of the action
* @param {String} action.displayName display name of the action, defaults
* to the name given in action.name
* @param {String} action.mime mime type
* @param {int} action.permissions permissions
* @param {(Function|String)} action.icon icon
* @param {Function} action.actionHandler function that performs the action
*/
registerAction: function (action) {
var mime = action.mime;
var name = action.name;
if (!this.actions[mime]) {
this.actions[mime] = {};
}
if (!this.actions[mime][name]) {
this.actions[mime][name] = {};
}
if (!displayName) {
displayName = t('files', name);
}
this.actions[mime][name]['action'] = action;
this.actions[mime][name]['permissions'] = permissions;
this.actions[mime][name]['displayName'] = displayName;
this.icons[name] = icon;
this._notifyUpdateListeners();
this.actions[mime][name] = {
action: action.actionHandler,
permissions: action.permissions,
displayName: action.displayName || t('files', name)
};
this.icons[name] = action.icon;
this._notifyUpdateListeners('registerAction', {action: action});
},
clear: function() {
this.actions = {};
@@ -112,7 +139,7 @@
},
setDefault: function (mime, name) {
this.defaults[mime] = name;
this._notifyUpdateListeners();
this._notifyUpdateListeners('setDefault', {defaultAction: {mime: mime, name: name}});
},
get: function (mime, type, permissions) {
var actions = this.getActions(mime, type, permissions);
@@ -264,14 +291,20 @@
if (actions['Delete']) {
var img = self.icons['Delete'];
var html;
var mountType = $tr.attr('data-mounttype');
var deleteTitle = t('files', 'Delete');
if (mountType === 'external-root') {
deleteTitle = t('files', 'Disconnect storage');
} else if (mountType === 'shared-root') {
deleteTitle = t('files', 'Unshare');
} else if (fileList.id === 'trashbin') {
deleteTitle = t('files', 'Delete permanently');
}
if (img.call) {
img = img(file);
}
if (typeof trashBinApp !== 'undefined' && trashBinApp) {
html = '<a href="#" original-title="' + t('files', 'Delete permanently') + '" class="action delete delete-icon" />';
} else {
html = '<a href="#" original-title="' + t('files', 'Delete') + '" class="action delete delete-icon" />';
}
html = '<a href="#" original-title="' + escapeHTML(deleteTitle) + '" class="action delete delete-icon" />';
var element = $(html);
element.data('action', actions['Delete']);
element.on('click', {a: null, elem: parent, actionFunc: actions['Delete'].action}, actionHandler);
@@ -314,7 +347,7 @@
});
this.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
var dir = context.fileList.getCurrentDirectory();
var dir = context.$file.attr('data-path') || context.fileList.getCurrentDirectory();
if (dir !== '/') {
dir = dir + '/';
}
+20 -5
View File
@@ -172,7 +172,8 @@
*/
destroy: function() {
// TODO: also unregister other event handlers
this.fileActions.removeUpdateListener(this._onFileActionsUpdated);
this.fileActions.off('registerAction', this._onFileActionsUpdated);
this.fileActions.off('setDefault', this._onFileActionsUpdated);
},
_initFileActions: function(fileActions) {
@@ -182,7 +183,8 @@
this.fileActions.registerDefaultActions();
}
this._onFileActionsUpdated = _.debounce(_.bind(this._onFileActionsUpdated, this), 100);
this.fileActions.addUpdateListener(this._onFileActionsUpdated);
this.fileActions.on('registerAction', this._onFileActionsUpdated);
this.fileActions.on('setDefault', this._onFileActionsUpdated);
},
/**
@@ -598,6 +600,10 @@
"data-permissions": fileData.permissions || this.getDirectoryPermissions()
});
if (fileData.mountType) {
tr.attr('data-mounttype', fileData.mountType);
}
if (!_.isUndefined(path)) {
tr.attr('data-path', path);
}
@@ -919,7 +925,9 @@
.addClass(direction === 'desc' ? this.SORT_INDICATOR_DESC_CLASS : this.SORT_INDICATOR_ASC_CLASS);
},
/**
* @brief Reloads the file list using ajax call
* Reloads the file list using ajax call
*
* @return ajax call object
*/
reload: function() {
this._selectedFiles = {};
@@ -945,6 +953,13 @@
this.hideMask();
if (!result || result.status === 'error') {
// if the error is not related to folder we're trying to load, reload the page to handle logout etc
if (result.data.error === 'authentication_error' ||
result.data.error === 'token_expired' ||
result.data.error === 'application_not_enabled'
) {
OC.redirect(OC.generateUrl('apps/files'));
}
OC.Notification.show(result.data.message);
return false;
}
@@ -968,7 +983,7 @@
}
this.setFiles(result.data.files);
return true
return true;
},
updateStorageStatistics: function(force) {
@@ -1566,7 +1581,7 @@
numMatch=base.match(/\((\d+)\)/);
var num=2;
if (numMatch && numMatch.length>0) {
num=parseInt(numMatch[numMatch.length-1])+1;
num=parseInt(numMatch[numMatch.length-1], 10)+1;
base=base.split('(');
base.pop();
base=$.trim(base.join('('));
+13
View File
@@ -37,6 +37,7 @@ class Helper
public static function determineIcon($file) {
if($file['type'] === 'dir') {
$icon = \OC_Helper::mimetypeIcon('dir');
// TODO: move this part to the client side, using mountType
if ($file->isShared()) {
$icon = \OC_Helper::mimetypeIcon('dir-shared');
} elseif ($file->isMounted()) {
@@ -125,6 +126,18 @@ class Helper
if (isset($i['is_share_mount_point'])) {
$entry['isShareMountPoint'] = $i['is_share_mount_point'];
}
$mountType = null;
if ($i->isShared()) {
$mountType = 'shared';
} else if ($i->isMounted()) {
$mountType = 'external';
}
if ($mountType !== null) {
if ($i->getInternalPath() === '') {
$mountType .= '-root';
}
$entry['mountType'] = $mountType;
}
return $entry;
}
+1 -3
View File
@@ -52,9 +52,7 @@ describe('OCA.Files.App tests', function() {
App.initialize();
});
afterEach(function() {
App.navigation = null;
App.fileList = null;
App.files = null;
App.destroy();
pushStateStub.restore();
parseUrlQueryStub.restore();
+153 -3
View File
@@ -193,6 +193,156 @@ describe('OCA.Files.FileActions tests', function() {
context = actionStub.getCall(0).args[1];
expect(context.dir).toEqual('/somepath');
});
describe('merging', function() {
var $tr;
beforeEach(function() {
var fileData = {
id: 18,
type: 'file',
name: 'testName.txt',
path: '/anotherpath/there',
mimetype: 'text/plain',
size: '1234',
etag: 'a01234c',
mtime: '123456'
};
$tr = fileList.add(fileData);
});
afterEach(function() {
$tr = null;
});
it('copies all actions to target file actions', function() {
var actions1 = new OCA.Files.FileActions();
var actions2 = new OCA.Files.FileActions();
var actionStub1 = sinon.stub();
var actionStub2 = sinon.stub();
actions1.register(
'all',
'Test',
OC.PERMISSION_READ,
OC.imagePath('core', 'actions/test'),
actionStub1
);
actions2.register(
'all',
'Test2',
OC.PERMISSION_READ,
OC.imagePath('core', 'actions/test'),
actionStub2
);
actions2.merge(actions1);
actions2.display($tr.find('td.filename'), true, fileList);
expect($tr.find('.action-test').length).toEqual(1);
expect($tr.find('.action-test2').length).toEqual(1);
$tr.find('.action-test').click();
expect(actionStub1.calledOnce).toEqual(true);
expect(actionStub2.notCalled).toEqual(true);
actionStub1.reset();
$tr.find('.action-test2').click();
expect(actionStub1.notCalled).toEqual(true);
expect(actionStub2.calledOnce).toEqual(true);
});
it('overrides existing actions on merge', function() {
var actions1 = new OCA.Files.FileActions();
var actions2 = new OCA.Files.FileActions();
var actionStub1 = sinon.stub();
var actionStub2 = sinon.stub();
actions1.register(
'all',
'Test',
OC.PERMISSION_READ,
OC.imagePath('core', 'actions/test'),
actionStub1
);
actions2.register(
'all',
'Test', // override
OC.PERMISSION_READ,
OC.imagePath('core', 'actions/test'),
actionStub2
);
actions1.merge(actions2);
actions1.display($tr.find('td.filename'), true, fileList);
expect($tr.find('.action-test').length).toEqual(1);
$tr.find('.action-test').click();
expect(actionStub1.notCalled).toEqual(true);
expect(actionStub2.calledOnce).toEqual(true);
});
it('overrides existing action when calling register after merge', function() {
var actions1 = new OCA.Files.FileActions();
var actions2 = new OCA.Files.FileActions();
var actionStub1 = sinon.stub();
var actionStub2 = sinon.stub();
actions1.register(
'all',
'Test',
OC.PERMISSION_READ,
OC.imagePath('core', 'actions/test'),
actionStub1
);
actions1.merge(actions2);
// late override
actions1.register(
'all',
'Test', // override
OC.PERMISSION_READ,
OC.imagePath('core', 'actions/test'),
actionStub2
);
actions1.display($tr.find('td.filename'), true, fileList);
expect($tr.find('.action-test').length).toEqual(1);
$tr.find('.action-test').click();
expect(actionStub1.notCalled).toEqual(true);
expect(actionStub2.calledOnce).toEqual(true);
});
it('leaves original file actions untouched (clean copy)', function() {
var actions1 = new OCA.Files.FileActions();
var actions2 = new OCA.Files.FileActions();
var actionStub1 = sinon.stub();
var actionStub2 = sinon.stub();
actions1.register(
'all',
'Test',
OC.PERMISSION_READ,
OC.imagePath('core', 'actions/test'),
actionStub1
);
// copy the Test action to actions2
actions2.merge(actions1);
// late override
actions2.register(
'all',
'Test', // override
OC.PERMISSION_READ,
OC.imagePath('core', 'actions/test'),
actionStub2
);
// check if original actions still call the correct handler
actions1.display($tr.find('td.filename'), true, fileList);
expect($tr.find('.action-test').length).toEqual(1);
$tr.find('.action-test').click();
expect(actionStub1.calledOnce).toEqual(true);
expect(actionStub2.notCalled).toEqual(true);
});
});
describe('events', function() {
var clock;
beforeEach(function() {
@@ -204,7 +354,7 @@ describe('OCA.Files.FileActions tests', function() {
it('notifies update event handlers once after multiple changes', function() {
var actionStub = sinon.stub();
var handler = sinon.stub();
FileActions.addUpdateListener(handler);
FileActions.on('registerAction', handler);
FileActions.register(
'all',
'Test',
@@ -224,8 +374,8 @@ describe('OCA.Files.FileActions tests', function() {
it('does not notifies update event handlers after unregistering', function() {
var actionStub = sinon.stub();
var handler = sinon.stub();
FileActions.addUpdateListener(handler);
FileActions.removeUpdateListener(handler);
FileActions.on('registerAction', handler);
FileActions.off('registerAction', handler);
FileActions.register(
'all',
'Test',
+26
View File
@@ -1933,4 +1933,30 @@ describe('OCA.Files.FileList tests', function() {
});
});
});
describe('Handeling errors', function () {
beforeEach(function () {
redirectStub = sinon.stub(OC, 'redirect');
fileList = new OCA.Files.FileList($('#app-content-files'));
});
afterEach(function () {
fileList = undefined;
redirectStub.restore();
});
it('reloads the page on authentication errors', function () {
fileList.reload();
fakeServer.requests[0].respond(
200,
{ 'Content-Type': 'application/json' },
JSON.stringify({
status: 'error',
data: {
'error': 'authentication_error'
}
})
);
expect(redirectStub.calledWith(OC.generateUrl('apps/files'))).toEqual(true);
});
});
});
+4
View File
@@ -10,6 +10,10 @@ OC::$CLASSPATH['OCA\Encryption\Session'] = 'files_encryption/lib/session.php';
OC::$CLASSPATH['OCA\Encryption\Capabilities'] = 'files_encryption/lib/capabilities.php';
OC::$CLASSPATH['OCA\Encryption\Helper'] = 'files_encryption/lib/helper.php';
// Exceptions
OC::$CLASSPATH['OCA\Encryption\Exceptions\MultiKeyEncryptException'] = 'files_encryption/lib/exceptions.php';
OC::$CLASSPATH['OCA\Encryption\Exceptions\MultiKeyDecryptException'] = 'files_encryption/lib/exceptions.php';
\OCP\Util::addscript('files_encryption', 'encryption');
\OCP\Util::addscript('files_encryption', 'detect-migration');
+1
View File
@@ -15,4 +15,5 @@
<types>
<filesystem/>
</types>
<ocsid>166047</ocsid>
</info>
+1 -1
View File
@@ -1 +1 @@
0.6
0.6.1
+1
View File
@@ -2,6 +2,7 @@
if (!isset($_)) { //also provide standalone error page
require_once __DIR__ . '/../../../lib/base.php';
require_once __DIR__ . '/../lib/crypt.php';
$l = OC_L10N::get('files_encryption');
+46 -33
View File
@@ -303,7 +303,7 @@ class Hooks {
}
/**
* @brief
* update share keys if a file was shared
*/
public static function postShared($params) {
@@ -313,34 +313,44 @@ class Hooks {
if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
$view = new \OC\Files\View('/');
$session = new \OCA\Encryption\Session($view);
$userId = \OCP\User::getUser();
$util = new Util($view, $userId);
$path = \OC\Files\Filesystem::getPath($params['fileSource']);
$sharingEnabled = \OCP\Share::isEnabled();
$mountManager = \OC\Files\Filesystem::getMountManager();
$mount = $mountManager->find('/' . $userId . '/files' . $path);
$mountPoint = $mount->getMountPoint();
// if a folder was shared, get a list of all (sub-)folders
if ($params['itemType'] === 'folder') {
$allFiles = $util->getAllFiles($path, $mountPoint);
} else {
$allFiles = array($path);
}
foreach ($allFiles as $path) {
$usersSharing = $util->getSharingUsersArray($sharingEnabled, $path);
$util->setSharedFileKeyfiles($session, $usersSharing, $path);
}
self::updateKeyfiles($path, $params['itemType']);
}
}
/**
* @brief
* update keyfiles and share keys recursively
*
* @param string $path to the file/folder
* @param string $type 'file' or 'folder'
*/
private static function updateKeyfiles($path, $type) {
$view = new \OC\Files\View('/');
$userId = \OCP\User::getUser();
$session = new \OCA\Encryption\Session($view);
$util = new Util($view, $userId);
$sharingEnabled = \OCP\Share::isEnabled();
$mountManager = \OC\Files\Filesystem::getMountManager();
$mount = $mountManager->find('/' . $userId . '/files' . $path);
$mountPoint = $mount->getMountPoint();
// if a folder was shared, get a list of all (sub-)folders
if ($type === 'folder') {
$allFiles = $util->getAllFiles($path, $mountPoint);
} else {
$allFiles = array($path);
}
foreach ($allFiles as $path) {
$usersSharing = $util->getSharingUsersArray($sharingEnabled, $path);
$util->setSharedFileKeyfiles($session, $usersSharing, $path);
}
}
/**
* unshare file/folder from a user with whom you shared the file before
*/
public static function postUnshare($params) {
@@ -385,8 +395,10 @@ class Hooks {
// Unshare every user who no longer has access to the file
$delUsers = array_diff($userIds, $sharingUsers);
list($owner, $ownerPath) = $util->getUidAndFilename($path);
// delete share key
Keymanager::delShareKey($view, $delUsers, $path);
Keymanager::delShareKey($view, $delUsers, $ownerPath, $owner);
}
}
@@ -432,7 +444,6 @@ class Hooks {
\OC_FileProxy::$enabled = false;
$view = new \OC\Files\View('/');
$session = new \OCA\Encryption\Session($view);
$userId = \OCP\User::getUser();
$util = new Util($view, $userId);
@@ -440,8 +451,9 @@ class Hooks {
isset(self::$renamedFiles[$params['oldpath']]['path'])) {
$ownerOld = self::$renamedFiles[$params['oldpath']]['uid'];
$pathOld = self::$renamedFiles[$params['oldpath']]['path'];
unset(self::$renamedFiles[$params['oldpath']]);
} else {
\OCP\Util::writeLog('Encryption library', "can't get path and owner from the file before it was renamed", \OCP\Util::ERROR);
\OCP\Util::writeLog('Encryption library', "can't get path and owner from the file before it was renamed", \OCP\Util::DEBUG);
return false;
}
@@ -474,6 +486,7 @@ class Hooks {
// handle share keys
if (!$view->is_dir($oldKeyfilePath)) {
$type = 'file';
$oldKeyfilePath .= '.key';
$newKeyfilePath .= '.key';
@@ -485,6 +498,7 @@ class Hooks {
}
} else {
$type = "folder";
// handle share-keys folders
$view->rename($oldShareKeyPath, $newShareKeyPath);
}
@@ -494,14 +508,9 @@ class Hooks {
$view->rename($oldKeyfilePath, $newKeyfilePath);
}
// update share keys
$sharingEnabled = \OCP\Share::isEnabled();
// get users
$usersSharing = $util->getSharingUsersArray($sharingEnabled, $pathNew);
// update sharing-keys
$util->setSharedFileKeyfiles($session, $usersSharing, $pathNew);
self::updateKeyfiles($params['newpath'], $type);
\OC_FileProxy::$enabled = $proxyStatus;
}
@@ -595,6 +604,7 @@ class Hooks {
}
/**
* unmount file from yourself
* remember files/folders which get unmounted
*/
public static function preUmount($params) {
@@ -613,6 +623,9 @@ class Hooks {
'itemType' => $itemType);
}
/**
* unmount file from yourself
*/
public static function postUmount($params) {
if (!isset(self::$umountedFiles[$params[\OC\Files\Filesystem::signal_param_path]])) {
@@ -642,7 +655,7 @@ class Hooks {
// check if the user still has access to the file, otherwise delete share key
$sharingUsers = \OCP\Share::getUsersSharingFile($path, $user);
if (!in_array(\OCP\User::getUser(), $sharingUsers['users'])) {
Keymanager::delShareKey($view, array(\OCP\User::getUser()), $path);
Keymanager::delShareKey($view, array(\OCP\User::getUser()), $path, $user);
}
}
}
+7 -16
View File
@@ -390,6 +390,7 @@ class Crypt {
* @param string $plainContent content to be encrypted
* @param array $publicKeys array keys must be the userId of corresponding user
* @return array keys: keys (array, key = userId), data
* @throws \OCA\Encryption\Exceptions\\MultiKeyEncryptException if encryption failed
* @note symmetricDecryptFileContent() can decrypt files created using this method
*/
public static function multiKeyEncrypt($plainContent, array $publicKeys) {
@@ -397,9 +398,7 @@ class Crypt {
// openssl_seal returns false without errors if $plainContent
// is empty, so trigger our own error
if (empty($plainContent)) {
throw new \Exception('Cannot mutliKeyEncrypt empty plain content');
throw new Exceptions\MultiKeyEncryptException('Cannot mutliKeyEncrypt empty plain content', 10);
}
// Set empty vars to be set by openssl by reference
@@ -426,9 +425,7 @@ class Crypt {
);
} else {
return false;
throw new Exceptions\MultiKeyEncryptException('multi key encryption failed: ' . openssl_error_string(), 20);
}
}
@@ -438,8 +435,8 @@ class Crypt {
* @param string $encryptedContent
* @param string $shareKey
* @param mixed $privateKey
* @return false|string
* @internal param string $plainContent content to be encrypted
* @throws \OCA\Encryption\Exceptions\\MultiKeyDecryptException if decryption failed
* @internal param string $plainContent contains decrypted content
* @return string $plainContent decrypted string
* @note symmetricDecryptFileContent() can be used to decrypt files created using this method
*
@@ -448,9 +445,7 @@ class Crypt {
public static function multiKeyDecrypt($encryptedContent, $shareKey, $privateKey) {
if (!$encryptedContent) {
return false;
throw new Exceptions\MultiKeyDecryptException('Cannot mutliKeyDecrypt empty plain content', 10);
}
if (openssl_open($encryptedContent, $plainContent, $shareKey, $privateKey)) {
@@ -458,11 +453,7 @@ class Crypt {
return $plainContent;
} else {
\OCP\Util::writeLog('Encryption library', 'Decryption (asymmetric) of sealed content with share-key "'.$shareKey.'" failed', \OCP\Util::ERROR);
return false;
throw new Exceptions\MultiKeyDecryptException('multiKeyDecrypt with share-key' . $shareKey . 'failed: ' . openssl_error_string(), 20);
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
/**
* ownCloud
*
* @author Bjoern Schiessle
* @copyright 2014 Bjoern Schiessle <schiessle@owncloud.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Encryption\Exceptions;
class EncryptionException extends \Exception {
}
/**
* Throw this exception if multi key encrytion fails
*
* Possible error codes:
* 10 - empty plain content was given
* 20 - openssl_seal failed
*/
class MultiKeyEncryptException extends EncryptionException {
}
/**
* Throw this encryption if multi key decryption failed
*
* Possible error codes:
* 10 - empty encrypted content was given
* 20 - openssl_open failed
*/
class MultiKeyDecryptException extends EncryptionException {
}
+7 -6
View File
@@ -444,17 +444,18 @@ class Keymanager {
/**
* Delete a single user's shareKey for a single file
*
* @param \OC\Files\View $view relative to data/
* @param array $userIds list of users we want to remove
* @param string $filename the owners name of the file for which we want to remove the users relative to data/user/files
* @param string $owner owner of the file
*/
public static function delShareKey(\OC\Files\View $view, $userIds, $filePath) {
public static function delShareKey($view, $userIds, $filename, $owner) {
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
$userId = Helper::getUser($filePath);
$util = new Util($view, $userId);
list($owner, $filename) = $util->getUidAndFilename($filePath);
$util = new Util($view, $owner);
if ($util->isSystemWideMountPoint($filename)) {
$shareKeyPath = \OC\Files\Filesystem::normalizePath('/files_encryption/share-keys/' . $filename);
+16 -9
View File
@@ -956,19 +956,26 @@ class Util {
// Get the current users's private key for decrypting existing keyfile
$privateKey = $session->getPrivateKey();
$fileOwner = \OC\Files\Filesystem::getOwner($filePath);
// Decrypt keyfile
$plainKeyfile = $this->decryptKeyfile($filePath, $privateKey);
// Re-enc keyfile to (additional) sharekeys
$multiEncKey = Crypt::multiKeyEncrypt($plainKeyfile, $userPubKeys);
try {
// Decrypt keyfile
$plainKeyfile = $this->decryptKeyfile($filePath, $privateKey);
// Re-enc keyfile to (additional) sharekeys
$multiEncKey = Crypt::multiKeyEncrypt($plainKeyfile, $userPubKeys);
} catch (Exceptions\EncryptionException $e) {
$msg = 'set shareFileKeyFailed (code: ' . $e->getCode() . '): ' . $e->getMessage();
\OCP\Util::writeLog('files_encryption', $msg, \OCP\Util::FATAL);
return false;
} catch (\Exception $e) {
$msg = 'set shareFileKeyFailed (unknown error): ' . $e->getMessage();
\OCP\Util::writeLog('files_encryption', $msg, \OCP\Util::FATAL);
return false;
}
// Save the recrypted key to it's owner's keyfiles directory
// Save new sharekeys to all necessary user directory
if (
!Keymanager::setFileKey($this->view, $this, $filePath, $multiEncKey['data'])
|| !Keymanager::setShareKeys($this->view, $this, $filePath, $multiEncKey['keys'])
!Keymanager::setFileKey($this->view, $this, $filePath, $multiEncKey['data'])
|| !Keymanager::setShareKeys($this->view, $this, $filePath, $multiEncKey['keys'])
) {
\OCP\Util::writeLog('Encryption library',
+2
View File
@@ -95,6 +95,8 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
} else {
OC_App::disable('files_trashbin');
}
$this->assertTrue(\OC_FileProxy::$enabled);
}
public static function tearDownAfterClass() {
+2 -2
View File
@@ -225,7 +225,7 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/subfolder/subsubfolder/file2.user3.shareKey', 'data');
// recursive delete share keys from user1 and user2
Encryption\Keymanager::delShareKey($this->view, array('user1', 'user2', Test_Encryption_Keymanager::TEST_USER), '/folder1/');
Encryption\Keymanager::delShareKey($this->view, array('user1', 'user2', Test_Encryption_Keymanager::TEST_USER), '/folder1/', Test_Encryption_Keymanager::TEST_USER);
// check if share keys from user1 and user2 are deleted
$this->assertFalse($this->view->file_exists(
@@ -274,7 +274,7 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey', 'data');
// recursive delete share keys from user1 and user2
Encryption\Keymanager::delShareKey($this->view, array('user1', 'user2', Test_Encryption_Keymanager::TEST_USER), '/folder1/existingFile.txt');
Encryption\Keymanager::delShareKey($this->view, array('user1', 'user2', Test_Encryption_Keymanager::TEST_USER), '/folder1/existingFile.txt', Test_Encryption_Keymanager::TEST_USER);
// check if share keys from user1 and user2 are deleted
$this->assertFalse($this->view->file_exists(
+37 -6
View File
@@ -50,10 +50,44 @@ class Test_Migration extends PHPUnit_Framework_TestCase {
}
public function testDataMigration() {
public function checkLastIndexId() {
$query = \OC_DB::prepare('INSERT INTO `*PREFIX*share` ('
.' `item_type`, `item_source`, `item_target`, `share_type`,'
.' `share_with`, `uid_owner`, `permissions`, `stime`, `file_source`,'
.' `file_target`, `token`, `parent`, `expiration`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)');
$query->bindValue(1, 'file');
$query->bindValue(2, 949);
$query->bindValue(3, '/949');
$query->bindValue(4, 0);
$query->bindValue(5, 'migrate-test-user');
$query->bindValue(6, 'migrate-test-owner');
$query->bindValue(7, 23);
$query->bindValue(8, 1402493312);
$query->bindValue(9, 0);
$query->bindValue(10, '/migration.txt');
$query->bindValue(11, null);
$query->bindValue(12, null);
$query->bindValue(13, null);
$this->assertEquals(1, $query->execute());
//FIXME fix this test so that we can enable it again
$this->markTestIncomplete('Disabled, because of this tests a lot of other tests fail at the moment');
$this->assertNotEquals('0', \OC_DB::insertid('*PREFIX*share'));
// cleanup
$query = \OC_DB::prepare('DELETE FROM `*PREFIX*share` WHERE `file_target` = ?');
$query->bindValue(1, '/migration.txt');
$this->assertEquals(1, $query->execute());
}
public function testBrokenLastIndexId() {
// create test table
$this->checkLastIndexId();
OC_DB::createDbFromStructure(__DIR__ . '/encryption_table.xml');
$this->checkLastIndexId();
}
public function testDataMigration() {
$this->assertTableNotExist('encryption_test');
@@ -80,9 +114,6 @@ class Test_Migration extends PHPUnit_Framework_TestCase {
public function testDuplicateDataMigration() {
//FIXME fix this test so that we can enable it again
$this->markTestIncomplete('Disabled, because of this tests a lot of other tests fail at the moment');
// create test table
OC_DB::createDbFromStructure(__DIR__ . '/encryption_table.xml');
+48
View File
@@ -1016,4 +1016,52 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
$this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename);
}
/**
* test if additional share keys are added if we move a folder to a shared parent
* @medium
*/
function testMoveFolder() {
$view = new \OC\Files\View('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1);
$filename = '/tmp-' . uniqid();
$folder = '/folder' . uniqid();
\OC\Files\Filesystem::mkdir($folder);
// Save long data as encrypted file using stream wrapper
$cryptedFile = \OC\Files\Filesystem::file_put_contents($folder . $filename, $this->dataShort);
// Test that data was successfully written
$this->assertTrue(is_int($cryptedFile));
// Get file decrypted contents
$decrypt = \OC\Files\Filesystem::file_get_contents($folder . $filename);
$this->assertEquals($this->dataShort, $decrypt);
$newFolder = '/newfolder/subfolder' . uniqid();
\OC\Files\Filesystem::mkdir('/newfolder');
// get the file info from previous created file
$fileInfo = \OC\Files\Filesystem::getFileInfo('/newfolder');
$this->assertTrue($fileInfo instanceof \OC\Files\FileInfo);
// share the folder
\OCP\Share::shareItem('folder', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, OCP\PERMISSION_ALL);
\OC\Files\Filesystem::rename($folder, $newFolder);
// Get file decrypted contents
$newDecrypt = \OC\Files\Filesystem::file_get_contents($newFolder . $filename);
$this->assertEquals($this->dataShort, $newDecrypt);
// check if additional share key for user2 exists
$this->assertTrue($view->file_exists('files_encryption/share-keys' . $newFolder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
// tear down
\OC\Files\Filesystem::unlink($newFolder);
\OC\Files\Filesystem::unlink('/newfolder');
}
}
+13 -6
View File
@@ -10,10 +10,17 @@ if ($_POST['isPersonal'] == 'true') {
OCP\JSON::checkAdminUser();
$isPersonal = false;
}
$status = OC_Mount_Config::addMountPoint($_POST['mountPoint'],
$_POST['class'],
$_POST['classOptions'],
$_POST['mountType'],
$_POST['applicable'],
$isPersonal);
$mountPoint = $_POST['mountPoint'];
$oldMountPoint = $_POST['oldMountPoint'];
$class = $_POST['class'];
$options = $_POST['classOptions'];
$type = $_POST['mountType'];
$applicable = $_POST['applicable'];
if ($oldMountPoint and $oldMountPoint !== $mountPoint) {
OC_Mount_Config::removeMountPoint($oldMountPoint, $type, $applicable, $isPersonal);
}
$status = OC_Mount_Config::addMountPoint($mountPoint, $class, $options, $type, $applicable, $isPersonal);
OCP\JSON::success(array('data' => array('message' => $status)));
+1
View File
@@ -10,4 +10,5 @@
<types>
<filesystem/>
</types>
<ocsid>166048</ocsid>
</info>
+1 -1
View File
@@ -1 +1 @@
0.2
0.2.1
+14 -1
View File
@@ -10,7 +10,20 @@ td.remove>img { visibility:hidden; padding-top:7px; }
tr:hover>td.remove>img { visibility:visible; cursor:pointer; }
#addMountPoint>td { border:none; }
#addMountPoint>td.applicable { visibility:hidden; }
#selectBackend { margin-left:-10px; }
#selectBackend {
margin-left: -10px;
width: 150px;
}
#externalStorage td.configuration,
#externalStorage td.backend {
white-space: normal;
}
#externalStorage td.configuration input.added {
margin-right: 6px;
}
#externalStorage label > input[type="checkbox"] {
margin-right: 3px;
+3 -7
View File
@@ -65,7 +65,6 @@
},
reload: function() {
var self = this;
this.showMask();
if (this._reloadCall) {
this._reloadCall.abort();
@@ -78,14 +77,10 @@
type: 'GET',
beforeSend: function(xhr) {
xhr.setRequestHeader('OCS-APIREQUEST', 'true');
},
error: function(result) {
self.reloadCallback(result);
},
success: function(result) {
self.reloadCallback(result);
}
});
var callBack = this.reloadCallback.bind(this);
return this._reloadCall.then(callBack, callBack);
},
reloadCallback: function(result) {
@@ -109,6 +104,7 @@
_makeFiles: function(data) {
var files = _.map(data, function(fileData) {
fileData.icon = OC.imagePath('core', 'filetypes/folder-external');
fileData.mountType = 'external';
return fileData;
});
+13 -8
View File
@@ -14,6 +14,7 @@ function updateStatus(statusEl, result){
OC.MountConfig={
saveStorage:function(tr, callback) {
var mountPoint = $(tr).find('.mountPoint input').val();
var oldMountPoint = $(tr).find('.mountPoint input').data('mountpoint');
if (mountPoint == '') {
return false;
}
@@ -80,9 +81,11 @@ OC.MountConfig={
classOptions: classOptions,
mountType: mountType,
applicable: applicable,
isPersonal: isPersonal
isPersonal: isPersonal,
oldMountPoint: oldMountPoint
},
success: function(result) {
$(tr).find('.mountPoint input').data('mountpoint', mountPoint);
status = updateStatus(statusSpan, result);
if (callback) {
callback(status);
@@ -139,9 +142,11 @@ OC.MountConfig={
classOptions: classOptions,
mountType: mountType,
applicable: applicable,
isPersonal: isPersonal
isPersonal: isPersonal,
oldMountPoint: oldMountPoint
},
success: function(result) {
$(tr).find('.mountPoint input').data('mountpoint', mountPoint);
status = updateStatus(statusSpan, result);
if (callback) {
callback(status);
@@ -187,15 +192,15 @@ $(document).ready(function() {
placeholder = placeholder.substring(1);
}
if (placeholder.indexOf('*') === 0) {
var class_string = is_optional ? ' class="optional"' : '';
td.append('<input type="password"' + class_string + ' data-parameter="'+parameter+'" placeholder="'+placeholder.substring(1)+'" />');
var class_string = is_optional ? ' optional' : '';
td.append('<input type="password" class="added' + class_string + '" data-parameter="'+parameter+'" placeholder="'+placeholder.substring(1)+'" />');
} else if (placeholder.indexOf('!') === 0) {
td.append('<label><input type="checkbox" data-parameter="'+parameter+'" />'+placeholder.substring(1)+'</label>');
td.append('<label><input type="checkbox" class="added" data-parameter="'+parameter+'" />'+placeholder.substring(1)+'</label>');
} else if (placeholder.indexOf('#') === 0) {
td.append('<input type="hidden" data-parameter="'+parameter+'" />');
td.append('<input type="hidden" class="added" data-parameter="'+parameter+'" />');
} else {
var class_string = is_optional ? ' class="optional"' : '';
td.append('<input type="text"' + class_string + ' data-parameter="'+parameter+'" placeholder="'+placeholder+'" />');
var class_string = is_optional ? ' optional' : '';
td.append('<input type="text" class="added' + class_string + '" data-parameter="'+parameter+'" placeholder="'+placeholder+'" />');
}
});
if (parameters['custom'] && $('#externalStorage tbody tr.'+backendClass.replace(/\\/g, '\\\\')).length == 1) {
+17 -13
View File
@@ -72,6 +72,12 @@ class AmazonS3 extends \OC\Files\Storage\Common {
sleep($this->timeout);
}
}
private function cleanKey($path) {
if ($path === '.') {
return '/';
}
return $path;
}
public function __construct($params) {
if (!isset($params['key']) || !isset($params['secret']) || !isset($params['bucket'])) {
@@ -118,11 +124,10 @@ class AmazonS3 extends \OC\Files\Storage\Common {
throw new \Exception("Creation of bucket failed.");
}
}
if (!$this->file_exists('.')) {
$result = $this->connection->putObject(array(
'Bucket' => $this->bucket,
'Key' => '.',
'Key' => $this->cleanKey('.'),
'Body' => '',
'ContentType' => 'httpd/unix-directory',
'ContentLength' => 0
@@ -167,7 +172,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
try {
$result = $this->connection->doesObjectExist(
$this->bucket,
$path
$this->cleanKey($path)
);
} catch (S3Exception $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
@@ -261,7 +266,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
$result = $this->connection->headObject(array(
'Bucket' => $this->bucket,
'Key' => $path
'Key' => $this->cleanKey($path)
));
$stat = array();
@@ -291,8 +296,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
if ($path != '.') {
$path .= '/';
}
if ($this->connection->doesObjectExist($this->bucket, $path)) {
if ($this->connection->doesObjectExist($this->bucket, $this->cleanKey($path))) {
return 'dir';
}
} catch (S3Exception $e) {
@@ -309,7 +313,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
try {
$result = $this->connection->deleteObject(array(
'Bucket' => $this->bucket,
'Key' => $path
'Key' => $this->cleanKey($path)
));
$this->testTimeout();
} catch (S3Exception $e) {
@@ -332,7 +336,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
try {
$result = $this->connection->getObject(array(
'Bucket' => $this->bucket,
'Key' => $path,
'Key' => $this->cleanKey($path),
'SaveAs' => $tmpFile
));
} catch (S3Exception $e) {
@@ -380,7 +384,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
try {
$result = $this->connection->headObject(array(
'Bucket' => $this->bucket,
'Key' => $path
'Key' => $this->cleanKey($path)
));
} catch (S3Exception $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
@@ -407,7 +411,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
}
$result = $this->connection->copyObject(array(
'Bucket' => $this->bucket,
'Key' => $path,
'Key' => $this->cleanKey($path),
'Metadata' => $metadata,
'CopySource' => $this->bucket . '/' . $path
));
@@ -415,7 +419,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
} else {
$result = $this->connection->putObject(array(
'Bucket' => $this->bucket,
'Key' => $path,
'Key' => $this->cleanKey($path),
'Metadata' => $metadata
));
$this->testTimeout();
@@ -436,7 +440,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
try {
$result = $this->connection->copyObject(array(
'Bucket' => $this->bucket,
'Key' => $path2,
'Key' => $this->cleanKey($path2),
'CopySource' => $this->bucket . '/' . $path1
));
$this->testTimeout();
@@ -535,7 +539,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
try {
$result= $this->connection->putObject(array(
'Bucket' => $this->bucket,
'Key' => self::$tmpFiles[$tmpFile],
'Key' => $this->cleanKey(self::$tmpFiles[$tmpFile]),
'SourceFile' => $tmpFile,
'ContentType' => \OC_Helper::getMimeType($tmpFile),
'ContentLength' => filesize($tmpFile)
+29 -8
View File
@@ -7,17 +7,35 @@
*/
namespace OC\Files\Storage;
/**
* Uses phpseclib's Net_SFTP class and the Net_SFTP_Stream stream wrapper to
* provide access to SFTP servers.
*/
class SFTP extends \OC\Files\Storage\Common {
private $host;
private $user;
private $password;
private $root;
/**
* @var \Net_SFTP
*/
private $client;
private static $tempFiles = array();
public function __construct($params) {
// The sftp:// scheme has to be manually registered via inclusion of
// the 'Net/SFTP/Stream.php' file which registers the Net_SFTP_Stream
// stream wrapper as a side effect.
// A slightly better way to register the stream wrapper is available
// since phpseclib 0.3.7 in the form of a static call to
// Net_SFTP_Stream::register() which will trigger autoloading if
// necessary.
// TODO: Call Net_SFTP_Stream::register() instead when phpseclib is
// updated to 0.3.7 or higher.
require_once 'Net/SFTP/Stream.php';
$this->host = $params['host'];
$proto = strpos($this->host, '://');
if ($proto != false) {
@@ -39,12 +57,8 @@ class SFTP extends \OC\Files\Storage\Common {
$hostKeys = $this->readHostKeys();
$this->client = new \Net_SFTP($this->host);
if (!$this->client->login($this->user, $this->password)) {
throw new \Exception('Login failed');
}
// The SSH Host Key MUST be verified before login().
$currentHostKey = $this->client->getServerPublicHostKey();
if (array_key_exists($this->host, $hostKeys)) {
if ($hostKeys[$this->host] != $currentHostKey) {
throw new \Exception('Host public key does not match known key');
@@ -53,6 +67,10 @@ class SFTP extends \OC\Files\Storage\Common {
$hostKeys[$this->host] = $currentHostKey;
$this->writeHostKeys($hostKeys);
}
if (!$this->client->login($this->user, $this->password)) {
throw new \Exception('Login failed');
}
}
public function test() {
@@ -216,8 +234,8 @@ class SFTP extends \OC\Files\Storage\Common {
case 'x+':
case 'c':
case 'c+':
// FIXME: make client login lazy to prevent it when using fopen()
return fopen($this->constructUrl($path), $mode);
$context = stream_context_create(array('sftp' => array('session' => $this->client)));
return fopen($this->constructUrl($path), $mode, false, $context);
}
} catch (\Exception $e) {
}
@@ -279,7 +297,10 @@ class SFTP extends \OC\Files\Storage\Common {
* @param string $path
*/
public function constructUrl($path) {
$url = 'sftp://'.$this->user.':'.$this->password.'@'.$this->host.$this->root.$path;
// Do not pass the password here. We want to use the Net_SFTP object
// supplied via stream context or fail. We only supply username and
// hostname because this might show up in logs (they are not used).
$url = 'sftp://'.$this->user.'@'.$this->host.$this->root.$path;
return $url;
}
}
+8 -7
View File
@@ -8,12 +8,11 @@
<th><?php p($l->t('Folder name')); ?></th>
<th><?php p($l->t('External storage')); ?></th>
<th><?php p($l->t('Configuration')); ?></th>
<!--<th><?php p($l->t('Options')); ?></th> -->
<?php if ($_['isAdminPage']) print_unescaped('<th>'.$l->t('Available for').'</th>'); ?>
<th>&nbsp;</th>
</tr>
</thead>
<tbody width="100%">
<tbody>
<?php $_['mounts'] = array_merge($_['mounts'], array('' => array())); ?>
<?php foreach ($_['mounts'] as $mount): ?>
<tr <?php print_unescaped(isset($mount['mountpoint']) ? 'class="'.OC_Util::sanitizeHTML($mount['class']).'"' : 'id="addMountPoint"'); ?>>
@@ -24,7 +23,9 @@
</td>
<td class="mountPoint"><input type="text" name="mountPoint"
value="<?php p(isset($mount['mountpoint']) ? $mount['mountpoint'] : ''); ?>"
placeholder="<?php p($l->t('Folder name')); ?>" /></td>
data-mountpoint="<?php p(isset($mount['mountpoint']) ? $mount['mountpoint'] : ''); ?>"
placeholder="<?php p($l->t('Folder name')); ?>" />
</td>
<?php if (!isset($mount['mountpoint'])): ?>
<td class="backend">
<select id="selectBackend" data-configurations='<?php p(json_encode($_['backends'])); ?>'>
@@ -36,10 +37,10 @@
</select>
</td>
<?php else: ?>
<td class="backend"
data-class="<?php p($mount['class']); ?>"><?php p($mount['backend']); ?></td>
<td class="backend" data-class="<?php p($mount['class']); ?>"><?php p($mount['backend']); ?>
</td>
<?php endif; ?>
<td class ="configuration" width="100%">
<td class ="configuration">
<?php if (isset($mount['options'])): ?>
<?php foreach ($mount['options'] as $parameter => $value): ?>
<?php if (isset($_['backends'][$mount['class']]['configuration'][$parameter])): ?>
@@ -149,7 +150,7 @@
action="<?php p(OCP\Util::linkTo('files_external', 'ajax/addRootCertificate.php')); ?>">
<h2><?php p($l->t('SSL root certificates'));?></h2>
<table id="sslCertificate" data-admin='<?php print_unescaped(json_encode($_['isAdminPage'])); ?>'>
<tbody width="100%">
<tbody>
<?php foreach ($_['certs'] as $rootCert): ?>
<tr id="<?php p($rootCert) ?>">
<td class="rootCert"><?php p($rootCert) ?></td>
+37
View File
@@ -800,4 +800,41 @@ class Test_Mount_Config extends \PHPUnit_Framework_TestCase {
$this->assertEquals($priority,
$mountPoints['/'.self::TEST_USER1.'/files/ext']['priority']);
}
/*
* Test for correct personal configuration loading in file sharing scenarios
*/
public function testMultiUserPersonalConfigLoading() {
$mountConfig = array(
'host' => 'somehost',
'user' => 'someuser',
'password' => 'somepassword',
'root' => 'someroot'
);
// Create personal mount point
$this->assertTrue(
OC_Mount_Config::addMountPoint(
'/ext',
'\OC\Files\Storage\SMB',
$mountConfig,
OC_Mount_Config::MOUNT_TYPE_USER,
self::TEST_USER1,
true
)
);
// Ensure other user can read mount points
\OC_User::setUserId(self::TEST_USER2);
$mountPointsMe = OC_Mount_Config::getAbsoluteMountPoints(self::TEST_USER2);
$mountPointsOther = OC_Mount_Config::getAbsoluteMountPoints(self::TEST_USER1);
$this->assertEquals(0, count($mountPointsMe));
$this->assertEquals(1, count($mountPointsOther));
$this->assertTrue(isset($mountPointsOther['/'.self::TEST_USER1.'/files/ext']));
$this->assertEquals('\OC\Files\Storage\SMB',
$mountPointsOther['/'.self::TEST_USER1.'/files/ext']['class']);
$this->assertEquals($mountConfig,
$mountPointsOther['/'.self::TEST_USER1.'/files/ext']['options']);
}
}
+1
View File
@@ -15,4 +15,5 @@
<files>public.php</files>
<webdav>publicwebdav.php</webdav>
</public>
<ocsid>166050</ocsid>
</info>
+1 -1
View File
@@ -1 +1 @@
0.5.2
0.5.3
+36 -9
View File
@@ -89,21 +89,48 @@ thead {
}
/* within #save */
#remote_address {
margin: 0;
height: 14px;
padding: 6px;
#save .save-form {
position: relative;
}
#save button {
#remote_address {
margin: 0;
width: 130px;
height: 14px;
padding: 6px;
padding-right: 24px;
}
.ie8 #remote_address {
padding-right: 30px;
}
#save #save-button,
#save #save-button-confirm {
margin: 0 5px;
height: 28px;
padding-bottom: 4px;
line-height: 14px;
}
#save .save-form [type="submit"] {
margin: 0 5px;
height: 28px;
padding-bottom: 4px;
#save-button-confirm {
position: absolute;
background-color: transparent;
border: none;
margin: 2px 4px !important;
right: 0;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
filter: alpha(opacity=50);
opacity: .5;
}
.ie8 #save-button-confirm {
margin: 2px 0 !important;
}
#save-button-confirm:hover,
#save-button-confirm:focus {
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
filter: alpha(opacity=100);
opacity: 1;
}
+40
View File
@@ -92,6 +92,21 @@ OCA.Sharing.App = {
}
},
/**
* Destroy the app
*/
destroy: function() {
OCA.Files.fileActions.off('setDefault.app-sharing', this._onActionsUpdated);
OCA.Files.fileActions.off('registerAction.app-sharing', this._onActionsUpdated);
this.removeSharingIn();
this.removeSharingOut();
this.removeSharingLinks();
this._inFileList = null;
this._outFileList = null;
this._linkFileList = null;
delete this._globalActionsInitialized;
},
_createFileActions: function() {
// inherit file actions from the files app
var fileActions = new OCA.Files.FileActions();
@@ -100,6 +115,14 @@ OCA.Sharing.App = {
fileActions.registerDefaultActions();
fileActions.merge(OCA.Files.fileActions);
if (!this._globalActionsInitialized) {
// in case actions are registered later
this._onActionsUpdated = _.bind(this._onActionsUpdated, this);
OCA.Files.fileActions.on('setDefault.app-sharing', this._onActionsUpdated);
OCA.Files.fileActions.on('registerAction.app-sharing', this._onActionsUpdated);
this._globalActionsInitialized = true;
}
// when the user clicks on a folder, redirect to the corresponding
// folder in the files app instead of opening it directly
fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
@@ -110,6 +133,23 @@ OCA.Sharing.App = {
return fileActions;
},
_onActionsUpdated: function(ev) {
_.each([this._inFileList, this._outFileList, this._linkFileList], function(list) {
if (!list) {
return;
}
if (ev.action) {
list.fileActions.registerAction(ev.action);
} else if (ev.defaultAction) {
list.fileActions.setDefault(
ev.defaultAction.mime,
ev.defaultAction.name
);
}
});
},
_extendFileList: function(fileList) {
// remove size column from summary
fileList.fileSummary.$el.find('.filesize').remove();
+31 -4
View File
@@ -42,13 +42,40 @@
}
};
if (!passwordProtected) {
OC.dialogs.confirm(t('files_sharing', 'Add {name} from {owner}@{remote}', {name: name, owner: owner, remote: remoteClean})
, t('files_sharing','Add Share'), callback, true);
OC.dialogs.confirm(
t(
'files_sharing',
'Do you want to add the remote share {name} from {owner}@{remote}?',
{name: name, owner: owner, remote: remoteClean}
),
t('files_sharing','Remote share'),
callback,
true
).then(this._adjustDialog);
} else {
OC.dialogs.prompt(t('files_sharing', 'Add {name} from {owner}@{remote}', {name: name, owner: owner, remote: remoteClean})
, t('files_sharing','Add Share'), callback, true, t('files_sharing','Password'), true);
OC.dialogs.prompt(
t(
'files_sharing',
'Do you want to add the remote share {name} from {owner}@{remote}?',
{name: name, owner: owner, remote: remoteClean}
),
t('files_sharing','Remote share'),
callback,
true,
t('files_sharing','Remote share password'),
true
).then(this._adjustDialog);
}
};
OCA.Sharing._adjustDialog = function() {
var $dialog = $('.oc-dialog:visible');
var $buttons = $dialog.find('button');
// hack the buttons
$dialog.find('.ui-icon').remove();
$buttons.eq(0).text(t('core', 'Cancel'));
$buttons.eq(1).text(t('core', 'Add remote share'));
};
})();
$(document).ready(function () {
+1 -1
View File
@@ -163,7 +163,7 @@ OCA.Sharing.PublicApp = {
OCA.Sharing.PublicApp._saveToOwnCloud(remote, token, owner, name, isProtected);
});
$('#save > button').click(function () {
$('#save #save-button').click(function () {
$(this).hide();
$('.save-form').css('display', 'inline');
$('#remote_address').focus();
+1 -1
View File
@@ -143,7 +143,7 @@
' data-action="Share-Notification" href="#" original-title="">' +
' <img class="svg" src="' + OC.imagePath('core', 'actions/share') + '"></img>';
$tr.find('.fileactions').append(function() {
var shareBy = t('files_sharing', 'Shared by {owner}', {owner: escapeHTML($tr.attr('data-share-owner'))});
var shareBy = escapeHTML($tr.attr('data-share-owner'));
var $result = $(shareNotification + '<span> ' + shareBy + '</span></span>');
$result.on('click', function() {
return false;
+12 -16
View File
@@ -95,7 +95,6 @@
},
reload: function() {
var self = this;
this.showMask();
if (this._reloadCall) {
this._reloadCall.abort();
@@ -110,14 +109,10 @@
type: 'GET',
beforeSend: function(xhr) {
xhr.setRequestHeader('OCS-APIREQUEST', 'true');
},
error: function(result) {
self.reloadCallback(result);
},
success: function(result) {
self.reloadCallback(result);
}
});
var callBack = this.reloadCallback.bind(this);
return this._reloadCall.then(callBack, callBack);
},
reloadCallback: function(result) {
@@ -166,11 +161,10 @@
}
else {
file.type = 'file';
// force preview retrieval as we don't have mime types,
// the preview endpoint will fall back to the mime type
// icon if no preview exists
file.isPreviewAvailable = true;
file.icon = true;
if (share.isPreviewAvailable) {
file.icon = true;
file.isPreviewAvailable = true;
}
}
file.share = {
id: share.id,
@@ -185,7 +179,9 @@
file.permissions = share.permissions;
}
else {
file.share.targetDisplayName = share.share_with_displayname;
if (share.share_type !== OC.Share.SHARE_TYPE_LINK) {
file.share.targetDisplayName = share.share_with_displayname;
}
file.name = OC.basename(share.path);
file.path = OC.dirname(share.path);
file.permissions = OC.PERMISSION_ALL;
@@ -237,6 +233,7 @@
.each(function(data) {
// convert the recipients map to a flat
// array of sorted names
data.mountType = 'shared';
data.recipients = _.keys(data.recipients);
data.recipientsDisplayName = OCA.Sharing.Util.formatRecipients(
data.recipients,
@@ -244,12 +241,11 @@
);
delete data.recipientsCount;
})
// Sort by expected sort comparator
.sortBy(this._sortComparator)
// Finish the chain by getting the result
.value();
return files;
// Sort by expected sort comparator
return files.sort(this._sortComparator);
}
});
+1 -1
View File
@@ -30,7 +30,7 @@ $TRANSLATIONS = array(
"Download %s" => "Download %s",
"Direct link" => "Directe link",
"Remote Shares" => "Externe shares",
"Allow other instances to mount public links shared from this server" => "Toestaan dat andere oanClouds openbaar gedeelde links mounten vanaf deze server",
"Allow other instances to mount public links shared from this server" => "Toestaan dat andere ownClouds openbaar gedeelde links mounten vanaf deze server",
"Allow users to mount public link shares" => "Toestaan dat gebruikers openbaar gedeelde links mounten"
);
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
+6
View File
@@ -60,6 +60,9 @@ class Api {
foreach ($shares as &$share) {
if ($share['item_type'] === 'file' && isset($share['path'])) {
$share['mimetype'] = \OC_Helper::getFileNameMimeType($share['path']);
if (\OC::$server->getPreviewManager()->isMimeSupported($share['mimetype'])) {
$share['isPreviewAvailable'] = true;
}
}
$newShares[] = $share;
}
@@ -214,6 +217,9 @@ class Api {
foreach ($shares as &$share) {
if ($share['item_type'] === 'file') {
$share['mimetype'] = \OC_Helper::getFileNameMimeType($share['file_target']);
if (\OC::$server->getPreviewManager()->isMimeSupported($share['mimetype'])) {
$share['isPreviewAvailable'] = true;
}
}
}
$result = new \OC_OCS_Result($shares);
+1 -1
View File
@@ -162,7 +162,7 @@ class Shared_Cache extends Cache {
foreach ($sourceFolderContent as $key => $c) {
$sourceFolderContent[$key]['path'] = $dir . $c['name'];
$sourceFolderContent[$key]['uid_owner'] = $parent['uid_owner'];
$sourceFolderContent[$key]['displayname_owner'] = $parent['uid_owner'];
$sourceFolderContent[$key]['displayname_owner'] = \OC_User::getDisplayName($parent['uid_owner']);
$sourceFolderContent[$key]['permissions'] = $sourceFolderContent[$key]['permissions'] & $this->storage->getPermissions($dir . $c['name']);
}
+10 -14
View File
@@ -21,47 +21,43 @@
*/
namespace OCA\Files\Share;
use OCA\Files_Sharing\Helper;
class Proxy extends \OC_FileProxy {
/**
* check if the deleted folder contains share mount points and move them
* up to the parent
* check if the deleted folder contains share mount points and unshare them
*
* @param string $path
*/
public function preUnlink($path) {
$this->moveMountPointsUp($path);
$this->unshareChildren($path);
}
/**
* check if the deleted folder contains share mount points and move them
* up to the parent
* check if the deleted folder contains share mount points and unshare them
*
* @param string $path
*/
public function preRmdir($path) {
$this->moveMountPointsUp($path);
$this->unshareChildren($path);
}
/**
* move share mount points up to the parent
* unshare shared items below the deleted folder
*
* @param string $path
*/
private function moveMountPointsUp($path) {
private function unshareChildren($path) {
$view = new \OC\Files\View('/');
// find share mount points within $path and move them up to the parent folder
// before we delete $path
// find share mount points within $path and unmount them
$mountManager = \OC\Files\Filesystem::getMountManager();
$mountedShares = $mountManager->findIn($path);
foreach ($mountedShares as $mount) {
if ($mount->getStorage()->instanceOfStorage('\OC\Files\Storage\Shared')) {
if ($mount->getStorage()->instanceOfStorage('OCA\Files_Sharing\ISharedStorage')) {
$mountPoint = $mount->getMountPoint();
$mountPointName = $mount->getMountPointName();
$target = \OCA\Files_Sharing\Helper::generateUniqueTarget(dirname($path) . '/' . $mountPointName, array(), $view);
$view->rename($mountPoint, $target);
$view->unlink($mountPoint);
}
}
}
+2
View File
@@ -143,8 +143,10 @@ class SharedMount extends Mount implements MoveableMount {
* @return bool
*/
public function removeMount() {
$mountManager = \OC\Files\Filesystem::getMountManager();
$storage = $this->getStorage();
$result = \OCP\Share::unshareFromSelf($storage->getItemType(), $storage->getMountPoint());
$mountManager->removeMount($this->mountPoint);
return $result;
}
+2 -2
View File
@@ -204,8 +204,8 @@ class Shared_Updater {
static public function fixBrokenSharesOnAppUpdate() {
// delete all shares where the original file no longer exists
$findAndRemoveShares = \OC_DB::prepare('DELETE FROM `*PREFIX*share` ' .
'WHERE `file_source` NOT IN ( ' .
'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `item_type` IN (\'file\', \'folder\'))'
'WHERE `item_type` IN (\'file\', \'folder\') ' .
'AND `file_source` NOT IN (SELECT `fileid` FROM `*PREFIX*filecache`)'
);
$findAndRemoveShares->execute(array());
}
-1
View File
@@ -67,7 +67,6 @@ $server->subscribeEvent('beforeMethod', function () use ($server, $objectTree, $
$mountManager = \OC\Files\Filesystem::getMountManager();
$objectTree->init($root, $view, $mountManager);
$server->addPlugin(new OC_Connector_Sabre_AbortedUploadDetectionPlugin($view));
$server->addPlugin(new OC_Connector_Sabre_QuotaPlugin($view));
}, 30); // priority 30: after auth (10) and acl(20), before lock(50) and handling the request
+2 -2
View File
@@ -19,10 +19,10 @@
<div class="header-right">
<span id="details">
<span id="save" data-protected="<?php p($_['protected'])?>" data-owner="<?php p($_['displayName'])?>" data-name="<?php p($_['filename'])?>">
<button><?php p($l->t('Add to your ownCloud')) ?></button>
<button id="save-button"><?php p($l->t('Add to your ownCloud')) ?></button>
<form class="save-form hidden" action="#">
<input type="text" id="remote_address" placeholder="example.com/owncloud"/>
<input type="submit" value="<?php p($l->t('Save')) ?>"/>
<button id="save-button-confirm" class="icon-confirm svg"></button>
</form>
</span>
<a href="<?php p($_['downloadURL']); ?>" id="download" class="button">
+19
View File
@@ -32,6 +32,9 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base {
function setUp() {
parent::setUp();
\OC_User::setDisplayName(self::TEST_FILES_SHARING_API_USER1, 'User One');
\OC_User::setDisplayName(self::TEST_FILES_SHARING_API_USER2, 'User Two');
self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
$this->user2View = new \OC\Files\View('/'. self::TEST_FILES_SHARING_API_USER2 . '/files');
@@ -133,11 +136,15 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base {
'name' => 'shareddir',
'path' => 'files/shareddir',
'mimetype' => 'httpd/unix-directory',
'uid_owner' => self::TEST_FILES_SHARING_API_USER1,
'displayname_owner' => 'User One',
),
array(
'name' => 'shared single file.txt',
'path' => 'files/shared single file.txt',
'mimetype' => 'text/plain',
'uid_owner' => self::TEST_FILES_SHARING_API_USER1,
'displayname_owner' => 'User One',
),
),
$results
@@ -153,16 +160,22 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base {
'name' => 'bar.txt',
'path' => 'bar.txt',
'mimetype' => 'text/plain',
'uid_owner' => self::TEST_FILES_SHARING_API_USER1,
'displayname_owner' => 'User One',
),
array(
'name' => 'emptydir',
'path' => 'emptydir',
'mimetype' => 'httpd/unix-directory',
'uid_owner' => self::TEST_FILES_SHARING_API_USER1,
'displayname_owner' => 'User One',
),
array(
'name' => 'subdir',
'path' => 'subdir',
'mimetype' => 'httpd/unix-directory',
'uid_owner' => self::TEST_FILES_SHARING_API_USER1,
'displayname_owner' => 'User One',
),
),
$results
@@ -187,16 +200,22 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base {
'name' => 'another too.txt',
'path' => 'another too.txt',
'mimetype' => 'text/plain',
'uid_owner' => self::TEST_FILES_SHARING_API_USER1,
'displayname_owner' => 'User One',
),
array(
'name' => 'another.txt',
'path' => 'another.txt',
'mimetype' => 'text/plain',
'uid_owner' => self::TEST_FILES_SHARING_API_USER1,
'displayname_owner' => 'User One',
),
array(
'name' => 'not a text file.xml',
'path' => 'not a text file.xml',
'mimetype' => 'application/xml',
'uid_owner' => self::TEST_FILES_SHARING_API_USER1,
'displayname_owner' => 'User One',
),
),
$results
+1 -6
View File
@@ -45,12 +45,7 @@ describe('OCA.Sharing.App tests', function() {
fileListOut = App.initSharingOut($('#app-content-sharingout'));
});
afterEach(function() {
App._inFileList = null;
App._outFileList = null;
fileListIn.destroy();
fileListOut.destroy();
fileListIn = null;
fileListOut = null;
App.destroy();
});
describe('initialization', function() {
+4 -4
View File
@@ -165,7 +165,7 @@ describe('OCA.Sharing.Util tests', function() {
$tr = fileList.$el.find('tbody tr:first');
$action = $tr.find('.action-share');
expect($action.hasClass('permanent')).toEqual(true);
expect($action.find('>span').text()).toEqual('Shared by User One');
expect($action.find('>span').text()).toEqual('User One');
expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg');
expect(OC.basename(getImageUrl($tr.find('.filename')))).toEqual('folder-shared.svg');
});
@@ -207,7 +207,7 @@ describe('OCA.Sharing.Util tests', function() {
expect($tr.find('.action-share').length).toEqual(0);
$action = $tr.find('.action-share-notification');
expect($action.hasClass('permanent')).toEqual(true);
expect($action.find('>span').text().trim()).toEqual('Shared by User One');
expect($action.find('>span').text().trim()).toEqual('User One');
expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg');
expect(OC.basename(getImageUrl($tr.find('.filename')))).toEqual('folder-shared.svg');
expect($action.find('img').length).toEqual(1);
@@ -369,7 +369,7 @@ describe('OCA.Sharing.Util tests', function() {
OC.Share.updateIcon('file', 1);
expect($action.hasClass('permanent')).toEqual(true);
expect($action.find('>span').text()).toEqual('Shared by User One');
expect($action.find('>span').text()).toEqual('User One');
expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg');
});
it('keep share text after unsharing reshare', function() {
@@ -405,7 +405,7 @@ describe('OCA.Sharing.Util tests', function() {
OC.Share.updateIcon('file', 1);
expect($action.hasClass('permanent')).toEqual(true);
expect($action.find('>span').text()).toEqual('Shared by User One');
expect($action.find('>span').text()).toEqual('User One');
expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg');
});
});
@@ -499,6 +499,48 @@ describe('OCA.Sharing.FileList tests', function() {
expect($tr.attr('data-permissions')).toEqual('31'); // read and delete
expect($tr.attr('data-mime')).toEqual('text/plain');
expect($tr.attr('data-mtime')).toEqual('11111000');
expect($tr.attr('data-share-recipients')).not.toBeDefined();
expect($tr.attr('data-share-owner')).not.toBeDefined();
expect($tr.attr('data-share-id')).toEqual('7');
expect($tr.find('a.name').attr('href')).toEqual(
OC.webroot +
'/index.php/apps/files/ajax/download.php' +
'?dir=%2Flocal%20path&files=local%20name.txt');
expect($tr.find('.nametext').text().trim()).toEqual('local name.txt');
});
it('does not show virtual token recipient as recipient when password was set', function() {
/* jshint camelcase: false */
var request;
// when a password is set, share_with contains an auth token
ocsResponse.ocs.data[0].share_with = 'abc01234/01234abc';
ocsResponse.ocs.data[0].share_with_displayname = 'abc01234/01234abc';
expect(fakeServer.requests.length).toEqual(1);
request = fakeServer.requests[0];
expect(request.url).toEqual(
OC.linkToOCS('apps/files_sharing/api/v1') +
'shares?format=json&shared_with_me=false'
);
fakeServer.requests[0].respond(
200,
{ 'Content-Type': 'application/json' },
JSON.stringify(ocsResponse)
);
// only renders the link share entry
var $rows = fileList.$el.find('tbody tr');
var $tr = $rows.eq(0);
expect($rows.length).toEqual(1);
expect($tr.attr('data-id')).toEqual('49');
expect($tr.attr('data-type')).toEqual('file');
expect($tr.attr('data-file')).toEqual('local name.txt');
expect($tr.attr('data-path')).toEqual('/local path');
expect($tr.attr('data-size')).not.toBeDefined();
expect($tr.attr('data-permissions')).toEqual('31'); // read and delete
expect($tr.attr('data-mime')).toEqual('text/plain');
expect($tr.attr('data-mtime')).toEqual('11111000');
expect($tr.attr('data-share-recipients')).not.toBeDefined();
expect($tr.attr('data-share-owner')).not.toBeDefined();
expect($tr.attr('data-share-id')).toEqual('7');
expect($tr.find('a.name').attr('href')).toEqual(
+14 -13
View File
@@ -47,7 +47,6 @@ class Test_Files_Sharing_Proxy extends Test_Files_Sharing_Base {
$this->filename = '/share-api-test';
// save file with content
$this->view->file_put_contents($this->filename, $this->data);
$this->view->mkdir($this->folder);
$this->view->mkdir($this->folder . $this->subfolder);
$this->view->mkdir($this->folder . $this->subfolder . $this->subsubfolder);
@@ -56,7 +55,6 @@ class Test_Files_Sharing_Proxy extends Test_Files_Sharing_Base {
}
function tearDown() {
$this->view->unlink($this->filename);
$this->view->deleteAll($this->folder);
self::$tempStorage = null;
@@ -69,30 +67,33 @@ class Test_Files_Sharing_Proxy extends Test_Files_Sharing_Base {
*/
function testpreUnlink() {
$fileInfo1 = \OC\Files\Filesystem::getFileInfo($this->filename);
$fileInfo2 = \OC\Files\Filesystem::getFileInfo($this->folder);
$result = \OCP\Share::shareItem('file', $fileInfo1->getId(), \OCP\Share::SHARE_TYPE_USER, self::TEST_FILES_SHARING_API_USER2, 31);
$this->assertTrue($result);
$result = \OCP\Share::shareItem('folder', $fileInfo2->getId(), \OCP\Share::SHARE_TYPE_USER, self::TEST_FILES_SHARING_API_USER2, 31);
$this->assertTrue($result);
self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
// move shared folder to 'localDir' and rename it, so that it uses the same
// name as the shared file
// one folder should be shared with the user
$sharedFolders = \OCP\Share::getItemsSharedWith('folder');
$this->assertSame(1, count($sharedFolders));
// move shared folder to 'localDir'
\OC\Files\Filesystem::mkdir('localDir');
$result = \OC\Files\Filesystem::rename($this->folder, '/localDir/' . $this->filename);
$result = \OC\Files\Filesystem::rename($this->folder, '/localDir/' . $this->folder);
$this->assertTrue($result);
\OC\Files\Filesystem::unlink('localDir');
self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
// after we deleted 'localDir' the share should be moved up to the root and be
// renamed to "filename (2)"
$this->assertTrue(\OC\Files\Filesystem::file_exists($this->filename));
$this->assertTrue(\OC\Files\Filesystem::file_exists($this->filename . ' (2)' ));
// after the parent directory was deleted the share should be unshared
$sharedFolders = \OCP\Share::getItemsSharedWith('folder');
$this->assertTrue(empty($sharedFolders));
self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
// the folder for the owner should still exists
$this->assertTrue(\OC\Files\Filesystem::file_exists($this->folder));
}
}
+22 -8
View File
@@ -87,13 +87,18 @@ class Test_Files_Sharing_Update_Routine extends Test_Files_Sharing_Base {
/**
* @medium
*/
function testRemoveBrokenShares() {
function testRemoveBrokenFileShares() {
$this->prepareFileCache();
// check if there are just 3 shares (see setUp - precondition: empty table)
$countShares = \OC_DB::prepare('SELECT COUNT(`id`) FROM `*PREFIX*share`');
$result = $countShares->execute()->fetchOne();
// check if there are just 4 shares (see setUp - precondition: empty table)
$countAllShares = \OC_DB::prepare('SELECT COUNT(`id`) FROM `*PREFIX*share`');
$result = $countAllShares->execute()->fetchOne();
$this->assertEquals(4, $result);
// check if there are just 3 file shares (see setUp - precondition: empty table)
$countFileShares = \OC_DB::prepare('SELECT COUNT(`id`) FROM `*PREFIX*share` WHERE `item_type` IN (\'file\', \'folder\')');
$result = $countFileShares->execute()->fetchOne();
$this->assertEquals(3, $result);
// check if there are just 2 items (see setUp - precondition: empty table)
@@ -105,19 +110,23 @@ class Test_Files_Sharing_Update_Routine extends Test_Files_Sharing_Base {
\OC\Files\Cache\Shared_Updater::fixBrokenSharesOnAppUpdate();
// check if there are just 2 shares (one gets killed by the code as there is no filecache entry for this)
$countShares = \OC_DB::prepare('SELECT COUNT(`id`) FROM `*PREFIX*share`');
$result = $countShares->execute()->fetchOne();
$result = $countFileShares->execute()->fetchOne();
$this->assertEquals(2, $result);
// check if the share of file '200' is removed as there is no entry for this in filecache table
$countShares = \OC_DB::prepare('SELECT COUNT(`id`) FROM `*PREFIX*share` WHERE `file_source` = 200');
$result = $countShares->execute()->fetchOne();
$countFileShares = \OC_DB::prepare('SELECT COUNT(`id`) FROM `*PREFIX*share` WHERE `file_source` = 200');
$result = $countFileShares->execute()->fetchOne();
$this->assertEquals(0, $result);
// check if there are just 2 items
$countItems = \OC_DB::prepare('SELECT COUNT(`fileid`) FROM `*PREFIX*filecache`');
$result = $countItems->execute()->fetchOne();
$this->assertEquals(2, $result);
// the calendar share survived
$countOtherShares = \OC_DB::prepare('SELECT COUNT(`id`) FROM `*PREFIX*share` WHERE `item_source` = \'999\'');
$result = $countOtherShares->execute()->fetchOne();
$this->assertEquals(1, $result);
}
/**
@@ -228,6 +237,11 @@ class Test_Files_Sharing_Update_Routine extends Test_Files_Sharing_Base {
$addShares->execute(array($fileIds[0]));
$addShares->execute(array(200)); // id of "deleted" file
$addShares->execute(array($fileIds[1]));
// add a few unrelated shares, calendar share that must be left untouched
$addShares = \OC_DB::prepare('INSERT INTO `*PREFIX*share` (`item_source`, `item_type`, `uid_owner`) VALUES (?, \'calendar\', 1)');
// the number is used as item_source
$addShares->execute(array(999));
}
}
+7 -7
View File
@@ -57,7 +57,7 @@ class Test_Files_Sharing_Updater extends Test_Files_Sharing_Base {
/**
* test deletion of a folder which contains share mount points. Share mount
* points should move up to the parent before the folder gets deleted so
* points should be unshared before the folder gets deleted so
* that the mount point doesn't end up at the trash bin
*/
function testDeleteParentFolder() {
@@ -78,6 +78,9 @@ class Test_Files_Sharing_Updater extends Test_Files_Sharing_Base {
// check if user2 can see the shared folder
$this->assertTrue($view->file_exists($this->folder));
$foldersShared = \OCP\Share::getItemsSharedWith('folder');
$this->assertSame(1, count($foldersShared));
$view->mkdir("localFolder");
$view->file_put_contents("localFolder/localFile.txt", "local file");
@@ -91,8 +94,9 @@ class Test_Files_Sharing_Updater extends Test_Files_Sharing_Base {
$this->loginHelper(self::TEST_FILES_SHARING_API_USER2);
// mount point should move up again
$this->assertTrue($view->file_exists($this->folder));
// shared folder should be unshared
$foldersShared = \OCP\Share::getItemsSharedWith('folder');
$this->assertTrue(empty($foldersShared));
// trashbin should contain the local file but not the mount point
$rootView = new \OC\Files\View('/' . self::TEST_FILES_SHARING_API_USER2);
@@ -109,10 +113,6 @@ class Test_Files_Sharing_Updater extends Test_Files_Sharing_Base {
if ($status === false) {
\OC_App::disable('files_trashbin');
}
// cleanup
$this->loginHelper(self::TEST_FILES_SHARING_API_USER1);
$result = \OCP\Share::unshare('folder', $fileinfo->getId(), \OCP\Share::SHARE_TYPE_USER, self::TEST_FILES_SHARING_API_USER2);
$this->assertTrue($result);
}
/**
+2
View File
@@ -1,6 +1,8 @@
<?php
$l = OC_L10N::get('files_trashbin');
OC::$CLASSPATH['OCA\Files_Trashbin\Exceptions\CopyRecursiveException'] = 'files_trashbin/lib/exceptions.php';
// register hooks
\OCA\Files_Trashbin\Trashbin::registerHooks();
+1
View File
@@ -24,4 +24,5 @@
<types>
<filesystem/>
</types>
<ocsid>166052</ocsid>
</info>
+1 -1
View File
@@ -1 +1 @@
0.6.1
0.6.2
+26
View File
@@ -0,0 +1,26 @@
<?php
/**
* ownCloud - trash bin
*
* @author Bjoern Schiessle
* @copyright 2014 Bjoern Schiessle schiessle@owncloud.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Trashbin\Exceptions;
class CopyRecursiveException extends \Exception {
}
+18 -6
View File
@@ -117,10 +117,18 @@ class Trashbin {
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
$sizeOfAddedFiles = self::copy_recursive('/files/' . $file_path, $trashPath, $view);
try {
$sizeOfAddedFiles = self::copy_recursive('/files/'.$file_path, $trashPath, $view);
} catch (\OCA\Files_Trashbin\Exceptions\CopyRecursiveException $e) {
$sizeOfAddedFiles = false;
if ($view->file_exists($trashPath)) {
$view->deleteAll($trashPath);
}
\OC_Log::write('files_trashbin', 'Couldn\'t move ' . $file_path . ' to the trash bin', \OC_log::ERROR);
}
\OC_FileProxy::$enabled = $proxyStatus;
if ($view->file_exists('files_trashbin/files/' . $filename . '.d' . $timestamp)) {
if ($sizeOfAddedFiles !== false) {
$size = $sizeOfAddedFiles;
$query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)");
$result = $query->execute(array($filename, $timestamp, $location, $user));
@@ -137,8 +145,6 @@ class Trashbin {
if ($user !== $owner) {
self::copyFilesToOwner($file_path, $owner, $ownerPath, $timestamp);
}
} else {
\OC_Log::write('files_trashbin', 'Couldn\'t move ' . $file_path . ' to the trash bin', \OC_log::ERROR);
}
$userTrashSize += $size;
@@ -823,13 +829,19 @@ class Trashbin {
$size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
} else {
$size += $view->filesize($pathDir);
$view->copy($pathDir, $destination . '/' . $i['name']);
$result = $view->copy($pathDir, $destination . '/' . $i['name']);
if (!$result) {
throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
}
$view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
}
}
} else {
$size += $view->filesize($source);
$view->copy($source, $destination);
$result = $view->copy($source, $destination);
if (!$result) {
throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
}
$view->touch($destination, $view->filemtime($source));
}
return $size;
+1
View File
@@ -29,4 +29,5 @@
<filesystem/>
</types>
<default_enable/>
<ocsid>166053</ocsid>
</info>
+3 -3
View File
@@ -5,11 +5,11 @@
* See the COPYING-README file.
*/
// Register with the capabilities API
OC_API::register('get', '/cloud/capabilities', array('OCA\Files_Versions\Capabilities', 'getCapabilities'), 'files_versions', OC_API::USER_AUTH);
/** @var $this \OCP\Route\IRouter */
$this->create('core_ajax_versions_preview', '/preview')->action(
function() {
require_once __DIR__ . '/../ajax/preview.php';
});
// Register with the capabilities API
OC_API::register('get', '/cloud/capabilities', array('OCA\Files_Versions\Capabilities', 'getCapabilities'), 'files_versions', OC_API::USER_AUTH);
+1 -1
View File
@@ -1 +1 @@
1.0.4
1.0.5
+24 -8
View File
@@ -131,6 +131,23 @@ class Storage {
'filename' => $filename);
}
/**
* delete the version from the storage and cache
*
* @param \OC\Files\View $view
* @param string $path
*/
protected static function deleteVersion($view, $path) {
$view->unlink($path);
/**
* @var \OC\Files\Storage\Storage $storage
* @var string $internalPath
*/
list($storage, $internalPath) = $view->resolvePath($path);
$cache = $storage->getCache($internalPath);
$cache->remove($internalPath);
}
/**
* Delete versions of a file
*/
@@ -142,15 +159,14 @@ class Storage {
if (!\OC\Files\Filesystem::file_exists($path)) {
$versions_fileview = new \OC\Files\View('/' . $uid . '/files_versions');
$view = new \OC\Files\View('/' . $uid . '/files_versions');
$abs_path = $versions_fileview->getLocalFile($filename . '.v');
$versions = self::getVersions($uid, $filename);
if (!empty($versions)) {
foreach ($versions as $v) {
\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $abs_path . $v['version']));
unlink($abs_path . $v['version']);
\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $abs_path . $v['version']));
\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path . $v['version']));
self::deleteVersion($view, $filename . '.v' . $v['version']);
\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path . $v['version']));
}
}
}
@@ -220,7 +236,7 @@ class Storage {
return true;
}else if ( $versionCreated ) {
$users_view->unlink($version);
self::deleteVersion($users_view, $version);
}
}
return false;
@@ -472,7 +488,7 @@ class Storage {
foreach($toDelete as $key => $path) {
\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path));
$versionsFileview->unlink($path);
self::deleteVersion($versionsFileview, $path);
\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path));
unset($allVersions[$key]); // update array with the versions we keep
\OCP\Util::writeLog('files_versions', "Expire: " . $path, \OCP\Util::DEBUG);
@@ -486,7 +502,7 @@ class Storage {
while ($availableSpace < 0 && $i < $numOfVersions) {
$version = current($allVersions);
\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version']));
$versionsFileview->unlink($version['path'].'.v'.$version['version']);
self::deleteVersion($versionsFileview, $version['path'] . '.v' . $version['version']);
\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version']));
\OCP\Util::writeLog('files_versions', 'running out of space! Delete oldest version: ' . $version['path'].'.v'.$version['version'] , \OCP\Util::DEBUG);
$versionsSize -= $version['size'];
+1
View File
@@ -17,4 +17,5 @@
<documentation>
<admin>http://doc.owncloud.org/server/6.0/go.php?to=admin-ldap</admin>
</documentation>
<ocsid>166061</ocsid>
</info>
+1 -1
View File
@@ -1 +1 @@
0.4.2
0.4.3
+198 -37
View File
@@ -50,20 +50,29 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
if(!$this->enabled) {
return false;
}
if($this->access->connection->isCached('inGroup'.$uid.':'.$gid)) {
return $this->access->connection->getFromCache('inGroup'.$uid.':'.$gid);
$cacheKey = 'inGroup'.$uid.':'.$gid;
if($this->access->connection->isCached($cacheKey)) {
return $this->access->connection->getFromCache($cacheKey);
}
$dn_user = $this->access->username2dn($uid);
$dn_group = $this->access->groupname2dn($gid);
$userDN = $this->access->username2dn($uid);
$groupDN = $this->access->groupname2dn($gid);
// just in case
if(!$dn_group || !$dn_user) {
$this->access->connection->writeToCache('inGroup'.$uid.':'.$gid, false);
if(!$groupDN || !$userDN) {
$this->access->connection->writeToCache($cacheKey, false);
return false;
}
//check primary group first
if($gid === $this->getUserPrimaryGroup($userDN)) {
$this->access->connection->writeToCache($cacheKey, true);
return true;
}
//usually, LDAP attributes are said to be case insensitive. But there are exceptions of course.
$members = array_keys($this->_groupMembers($dn_group));
$members = array_keys($this->_groupMembers($groupDN));
if(!$members) {
$this->access->connection->writeToCache('inGroup'.$uid.':'.$gid, false);
$this->access->connection->writeToCache($cacheKey, false);
return false;
}
@@ -82,8 +91,8 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
$members = $dns;
}
$isInGroup = in_array($dn_user, $members);
$this->access->connection->writeToCache('inGroup'.$uid.':'.$gid, $isInGroup);
$isInGroup = in_array($userDN, $members);
$this->access->connection->writeToCache($cacheKey, $isInGroup);
return $isInGroup;
}
@@ -91,6 +100,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
/**
* @param string $dnGroup
* @param array|null &$seen
* @return array|mixed|null
*/
private function _groupMembers($dnGroup, &$seen = null) {
if ($seen === null) {
@@ -125,6 +135,125 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
return $allMembers;
}
/**
* translates a primary group ID into an ownCloud internal name
* @param string $gid as given by primaryGroupID on AD
* @param string $dn a DN that belongs to the same domain as the group
* @return string|bool
*/
public function primaryGroupID2Name($gid, $dn) {
$cacheKey = 'primaryGroupIDtoName';
if($this->access->connection->isCached($cacheKey)) {
$groupNames = $this->access->connection->getFromCache($cacheKey);
if(isset($groupNames[$gid])) {
return $groupNames[$gid];
}
}
$domainObjectSid = $this->access->getSID($dn);
if($domainObjectSid === false) {
return false;
}
//we need to get the DN from LDAP
$filter = $this->access->combineFilterWithAnd(array(
$this->access->connection->ldapGroupFilter,
'objectsid=' . $domainObjectSid . '-' . $gid
));
$result = $this->access->searchGroups($filter, array('dn'), 1);
if(empty($result)) {
return false;
}
$dn = $result[0];
//and now the group name
//NOTE once we have separate ownCloud group IDs and group names we can
//directly read the display name attribute instead of the DN
$name = $this->access->dn2groupname($dn);
$this->access->connection->writeToCache($cacheKey, $name);
return $name;
}
/**
* returns the entry's primary group ID
* @param string $dn
* @param string $attribute
* @return string|bool
*/
private function getEntryGroupID($dn, $attribute) {
$value = $this->access->readAttribute($dn, $attribute);
if(is_array($value) && !empty($value)) {
return $value[0];
}
return false;
}
/**
* returns the group's primary ID
* @param string $dn
* @return string|bool
*/
public function getGroupPrimaryGroupID($dn) {
return $this->getEntryGroupID($dn, 'primaryGroupToken');
}
/**
* returns the user's primary group ID
* @param string $dn
* @return string|bool
*/
public function getUserPrimaryGroupIDs($dn) {
return $this->getEntryGroupID($dn, 'primaryGroupID');
}
/**
* returns a list of users that have the given group as primary group
*
* @param string $groupDN
* @param $limit
* @param int $offset
* @return string[]
*/
public function getUsersInPrimaryGroup($groupDN, $limit = -1, $offset = 0) {
$groupID = $this->getGroupPrimaryGroupID($groupDN);
if($groupID === false) {
return array();
}
$filter = $this->access->combineFilterWithAnd(array(
$this->access->connection->ldapUserFilter,
'primaryGroupID=' . $groupID
));
$users = $this->access->fetchListOfUsers(
$filter,
array($this->access->connection->ldapUserDisplayName, 'dn'),
$limit,
$offset
);
return $users;
}
/**
* gets the primary group of a user
* @param string $dn
* @return string
*/
public function getUserPrimaryGroup($dn) {
$groupID = $this->getUserPrimaryGroupIDs($dn);
if($groupID !== false) {
$groupName = $this->primaryGroupID2Name($groupID, $dn);
if($groupName !== false) {
return $groupName;
}
}
return false;
}
/**
* Get all groups a user belongs to
* @param string $uid Name of the user
@@ -161,7 +290,14 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
}
$groups = array_values($this->getGroupsByMember($uid));
$groups = array_unique($this->access->ownCloudGroupNames($groups), SORT_LOCALE_STRING);
$groups = $this->access->ownCloudGroupNames($groups);
$primaryGroup = $this->getUserPrimaryGroup($userDN);
if($primaryGroup !== false) {
$groups[] = $primaryGroup;
}
$groups = array_unique($groups, SORT_LOCALE_STRING);
$this->access->connection->writeToCache($cacheKey, $groups);
return $groups;
@@ -170,6 +306,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
/**
* @param string $dn
* @param array|null &$seen
* @return array
*/
private function getGroupsByMember($dn, &$seen = null) {
if ($seen === null) {
@@ -205,6 +342,11 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
/**
* get a list of all users in a group
*
* @param string $gid
* @param string $search
* @param int $limit
* @param int $offset
* @return array with user ids
*/
public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
@@ -214,9 +356,9 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
if(!$this->groupExists($gid)) {
return array();
}
$cachekey = 'usersInGroup-'.$gid.'-'.$search.'-'.$limit.'-'.$offset;
$cacheKey = 'usersInGroup-'.$gid.'-'.$search.'-'.$limit.'-'.$offset;
// check for cache of the exact query
$groupUsers = $this->access->connection->getFromCache($cachekey);
$groupUsers = $this->access->connection->getFromCache($cacheKey);
if(!is_null($groupUsers)) {
return $groupUsers;
}
@@ -225,7 +367,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
$groupUsers = $this->access->connection->getFromCache('usersInGroup-'.$gid.'-'.$search);
if(!is_null($groupUsers)) {
$groupUsers = array_slice($groupUsers, $offset, $limit);
$this->access->connection->writeToCache($cachekey, $groupUsers);
$this->access->connection->writeToCache($cacheKey, $groupUsers);
return $groupUsers;
}
@@ -235,14 +377,14 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
$groupDN = $this->access->groupname2dn($gid);
if(!$groupDN) {
// group couldn't be found, return empty resultset
$this->access->connection->writeToCache($cachekey, array());
$this->access->connection->writeToCache($cacheKey, array());
return array();
}
$members = array_keys($this->_groupMembers($groupDN));
if(!$members) {
//in case users could not be retrieved, return empty resultset
$this->access->connection->writeToCache($cachekey, array());
//in case users could not be retrieved, return empty result set
$this->access->connection->writeToCache($cacheKey, array());
return array();
}
@@ -250,7 +392,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
$isMemberUid = (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid');
foreach($members as $member) {
if($isMemberUid) {
//we got uids, need to get their DNs to 'tranlsate' them to usernames
//we got uids, need to get their DNs to 'translate' them to user names
$filter = $this->access->combineFilterWithAnd(array(
\OCP\Util::mb_str_replace('%uid', $member,
$this->access->connection->ldapLoginFilter, 'UTF-8'),
@@ -276,10 +418,16 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
}
}
}
natsort($groupUsers);
$this->access->connection->writeToCache('usersInGroup-'.$gid.'-'.$search, $groupUsers);
$groupUsers = array_slice($groupUsers, $offset, $limit);
$this->access->connection->writeToCache($cachekey, $groupUsers);
//and get users that have the group as primary
$primaryUsers = $this->getUsersInPrimaryGroup($groupDN, $limit, $offset);
$groupUsers = array_unique(array_merge($groupUsers, $primaryUsers));
$this->access->connection->writeToCache($cacheKey, $groupUsers);
return $groupUsers;
}
@@ -291,32 +439,32 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
* @return int|bool
*/
public function countUsersInGroup($gid, $search = '') {
$cachekey = 'countUsersInGroup-'.$gid.'-'.$search;
$cacheKey = 'countUsersInGroup-'.$gid.'-'.$search;
if(!$this->enabled || !$this->groupExists($gid)) {
return false;
}
$groupUsers = $this->access->connection->getFromCache($cachekey);
$groupUsers = $this->access->connection->getFromCache($cacheKey);
if(!is_null($groupUsers)) {
return $groupUsers;
}
$groupDN = $this->access->groupname2dn($gid);
if(!$groupDN) {
// group couldn't be found, return empty resultset
$this->access->connection->writeToCache($cachekey, false);
// group couldn't be found, return empty result set
$this->access->connection->writeToCache($cacheKey, false);
return false;
}
$members = array_keys($this->_groupMembers($groupDN));
if(!$members) {
//in case users could not be retrieved, return empty resultset
$this->access->connection->writeToCache($cachekey, false);
//in case users could not be retrieved, return empty result set
$this->access->connection->writeToCache($cacheKey, false);
return false;
}
if(empty($search)) {
$groupUsers = count($members);
$this->access->connection->writeToCache($cachekey, $groupUsers);
$this->access->connection->writeToCache($cacheKey, $groupUsers);
return $groupUsers;
}
$isMemberUid =
@@ -334,7 +482,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
$groupUsers = array();
foreach($members as $member) {
if($isMemberUid) {
//we got uids, need to get their DNs to 'tranlsate' them to usernames
//we got uids, need to get their DNs to 'translate' them to user names
$filter = $this->access->combineFilterWithAnd(array(
\OCP\Util::mb_str_replace('%uid', $member,
$this->access->connection->ldapLoginFilter, 'UTF-8'),
@@ -359,11 +507,19 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
}
}
//and get users that have the group as primary
$primaryUsers = $this->getUsersInPrimaryGroup($groupDN);
$groupUsers = array_unique(array_merge($groupUsers, $primaryUsers));
return count($groupUsers);
}
/**
* get a list of all groups
*
* @param string $search
* @param $limit
* @param int $offset
* @return array with group names
*
* Returns a list with all groups (used by getGroups)
@@ -372,11 +528,11 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
if(!$this->enabled) {
return array();
}
$cachekey = 'getGroups-'.$search.'-'.$limit.'-'.$offset;
$cacheKey = 'getGroups-'.$search.'-'.$limit.'-'.$offset;
//Check cache before driving unnecessary searches
\OCP\Util::writeLog('user_ldap', 'getGroups '.$cachekey, \OCP\Util::DEBUG);
$ldap_groups = $this->access->connection->getFromCache($cachekey);
\OCP\Util::writeLog('user_ldap', 'getGroups '.$cacheKey, \OCP\Util::DEBUG);
$ldap_groups = $this->access->connection->getFromCache($cacheKey);
if(!is_null($ldap_groups)) {
return $ldap_groups;
}
@@ -397,26 +553,30 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
$offset);
$ldap_groups = $this->access->ownCloudGroupNames($ldap_groups);
$this->access->connection->writeToCache($cachekey, $ldap_groups);
$this->access->connection->writeToCache($cacheKey, $ldap_groups);
return $ldap_groups;
}
/**
* get a list of all groups using a paged search
*
* @param string $search
* @param int $limit
* @param int $offset
* @return array with group names
*
* Returns a list with all groups
* Uses a paged search if available to override a
* server side search limit.
* (active directory has a limit of 1000 by default)
* Uses a paged search if available to override a
* server side search limit.
* (active directory has a limit of 1000 by default)
*/
public function getGroups($search = '', $limit = -1, $offset = 0) {
if(!$this->enabled) {
return array();
}
$pagingsize = $this->access->connection->ldapPagingSize;
$pagingSize = $this->access->connection->ldapPagingSize;
if ((! $this->access->connection->hasPagedResultSupport)
|| empty($pagingsize)) {
|| empty($pagingSize)) {
return $this->getGroupsChunk($search, $limit, $offset);
}
$maxGroups = 100000; // limit max results (just for safety reasons)
@@ -428,7 +588,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
$chunkOffset = $offset;
$allGroups = array();
while ($chunkOffset < $overallLimit) {
$chunkLimit = min($pagingsize, $overallLimit - $chunkOffset);
$chunkLimit = min($pagingSize, $overallLimit - $chunkOffset);
$ldapGroups = $this->getGroupsChunk($search, $chunkLimit, $chunkOffset);
$nread = count($ldapGroups);
\OCP\Util::writeLog('user_ldap', 'getGroups('.$search.'): read '.$nread.' at offset '.$chunkOffset.' (limit: '.$chunkLimit.')', \OCP\Util::DEBUG);
@@ -445,6 +605,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
/**
* @param string $group
* @return bool
*/
public function groupMatchesFilter($group) {
return (strripos($group, $this->groupSearch) !== false);
+95 -5
View File
@@ -28,6 +28,9 @@ namespace OCA\user_ldap\lib;
* @package OCA\user_ldap\lib
*/
class Access extends LDAPUtility implements user\IUserTools {
/**
* @var \OCA\user_ldap\lib\Connection
*/
public $connection;
public $userManager;
//never ever check this var directly, always use getPagedSearchResultState
@@ -61,8 +64,8 @@ class Access extends LDAPUtility implements user\IUserTools {
/**
* reads a given attribute for an LDAP record identified by a DN
* @param $dn the record in question
* @param $attr the attribute that shall be retrieved
* @param string $dn the record in question
* @param string $attr the attribute that shall be retrieved
* if empty, just check the record's existence
* @param string $filter
* @return array|false an array of values on success or an empty
@@ -180,6 +183,33 @@ class Access extends LDAPUtility implements user\IUserTools {
return $dn;
}
/**
* returns a DN-string that is cleaned from not domain parts, e.g.
* cn=foo,cn=bar,dc=foobar,dc=server,dc=org
* becomes dc=foobar,dc=server,dc=org
* @param string $dn
* @return string
*/
public function getDomainDNFromDN($dn) {
$allParts = $this->ldap->explodeDN($dn, 0);
if($allParts === false) {
//not a valid DN
return '';
}
$domainParts = array();
$dcFound = false;
foreach($allParts as $part) {
if(!$dcFound && strpos($part, 'dc=') === 0) {
$dcFound = true;
}
if($dcFound) {
$domainParts[] = $part;
}
}
$domainDN = implode(',', $domainParts);
return $domainDN;
}
/**
* gives back the database table for the query
* @param bool $isUser
@@ -534,7 +564,7 @@ class Access extends LDAPUtility implements user\IUserTools {
if(!\OC_Group::groupExists($altName)) {
return $altName;
}
$altName = $name . '_' . $lastNo + $attempts;
$altName = $name . '_' . ($lastNo + $attempts);
$attempts++;
}
return false;
@@ -581,6 +611,7 @@ class Access extends LDAPUtility implements user\IUserTools {
/**
* @param boolean $isUsers
* @return array
*/
private function mappedComponents($isUsers) {
$table = $this->getMapTable($isUsers);
@@ -834,7 +865,7 @@ class Access extends LDAPUtility implements user\IUserTools {
private function count($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
\OCP\Util::writeLog('user_ldap', 'Count filter: '.print_r($filter, true), \OCP\Util::DEBUG);
if(is_null($limit)) {
if(is_null($limit) || $limit <= 0) {
$limit = intval($this->connection->ldapPagingSize);
}
@@ -894,6 +925,10 @@ class Access extends LDAPUtility implements user\IUserTools {
* @return array with the search result
*/
private function search($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
if($limit <= 0) {
//otherwise search will fail
$limit = null;
}
$search = $this->executeSearch($filter, $base, $attr, $limit, $offset);
if($search === false) {
return array();
@@ -908,7 +943,7 @@ class Access extends LDAPUtility implements user\IUserTools {
$this->processPagedSearchStatus($sr, $filter, $base, 1, $limit,
$offset, $pagedSearchOK,
$skipHandling);
return;
return array();
}
// Do the server-side sorting
@@ -1232,6 +1267,61 @@ class Access extends LDAPUtility implements user\IUserTools {
return strtoupper($hex_guid_to_guid_str);
}
/**
* gets a SID of the domain of the given dn
* @param string $dn
* @return string|bool
*/
public function getSID($dn) {
$domainDN = $this->getDomainDNFromDN($dn);
$cacheKey = 'getSID-'.$domainDN;
if($this->connection->isCached($cacheKey)) {
return $this->connection->getFromCache($cacheKey);
}
$objectSid = $this->readAttribute($domainDN, 'objectsid');
if(!is_array($objectSid) || empty($objectSid)) {
$this->connection->writeToCache($cacheKey, false);
return false;
}
$domainObjectSid = $this->convertSID2Str($objectSid[0]);
$this->connection->writeToCache($cacheKey, $domainObjectSid);
return $domainObjectSid;
}
/**
* converts a binary SID into a string representation
* @param string $sid
* @return string
* @link http://blogs.freebsdish.org/tmclaugh/2010/07/21/finding-a-users-primary-group-in-ad/#comment-2855
*/
public function convertSID2Str($sid) {
try {
if(!function_exists('bcadd')) {
\OCP\Util::writeLog('user_ldap',
'You need to install bcmath module for PHP to have support ' .
'for AD primary groups', \OCP\Util::WARN);
throw new \Exception('missing bcmath module');
}
$srl = ord($sid[0]);
$numberSubID = ord($sid[1]);
$x = substr($sid, 2, 6);
$h = unpack('N', "\x0\x0" . substr($x,0,2));
$l = unpack('N', substr($x,2,6));
$iav = bcadd(bcmul($h[1], bcpow(2,32)), $l[1]);
$subIDs = array();
for ($i=0; $i<$numberSubID; $i++) {
$subID = unpack('V', substr($sid, 8+4*$i, 4));
$subIDs[] = $subID[1];
}
} catch (\Exception $e) {
return '';
}
return sprintf('S-%d-%d-%s', $srl, $iav, implode('-', $subIDs));
}
/**
* converts a stored DN so it can be used as base parameter for LDAP queries, internally we store them for usage in LDAP filters
* @param string $dn the DN
+7
View File
@@ -23,6 +23,13 @@
namespace OCA\user_ldap\lib;
//magic properties (incomplete)
/**
* responsible for LDAP connections in context with the provided configuration
* @property string ldapUserFilter
* @property string ldapUserDisplayName
* @property boolean hasPagedResultSupport
*/
class Connection extends LDAPUtility {
private $ldapConnectionRes = null;
private $configPrefix;
+9
View File
@@ -89,6 +89,15 @@ interface ILDAPWrapper {
*/
public function error($link);
/**
* Splits DN into its component parts
* @param string $dn
* @param int @withAttrib
* @return array|false
* @link http://www.php.net/manual/en/function.ldap-explode-dn.php
*/
public function explodeDN($dn, $withAttrib);
/**
* Return first result id
* @param resource $link LDAP link resource
+11
View File
@@ -98,6 +98,17 @@ class LDAP implements ILDAPWrapper {
return $this->invokeLDAPMethod('error', $link);
}
/**
* Splits DN into its component parts
* @param string $dn
* @param int @withAttrib
* @return array|false
* @link http://www.php.net/manual/en/function.ldap-explode-dn.php
*/
public function explodeDN($dn, $withAttrib) {
return $this->invokeLDAPMethod('ldap_explode_dn', $dn, $withAttrib);
}
/**
* @param LDAP $link
* @param LDAP $result
+2 -1
View File
@@ -143,7 +143,8 @@ class Manager {
return $this->users['byUid'][$id];
}
if(strpos(mb_strtolower($id, 'UTF-8'), 'dc=') === false) {
if(strpos(mb_strtolower($id, 'UTF-8'), 'dc=') === false
&& strpos(mb_strtolower($id, 'UTF-8'), 'uid=') === false ) {
//most likely a uid
$dn = $this->access->username2dn($id);
if($dn !== false) {
+80 -1
View File
@@ -77,4 +77,83 @@ class Test_Access extends \PHPUnit_Framework_TestCase {
$expected = 'foo\\\\*bar';
$this->assertTrue($expected === $access->escapeFilterPart($input));
}
}
public function testConvertSID2StrSuccess() {
list($lw, $con, $um) = $this->getConnecterAndLdapMock();
$access = new Access($con, $lw, $um);
if(!function_exists('\bcadd')) {
$this->markTestSkipped('bcmath not available');
}
$sidBinary = file_get_contents(__DIR__ . '/data/sid.dat');
$sidExpected = 'S-1-5-21-249921958-728525901-1594176202';
$this->assertSame($sidExpected, $access->convertSID2Str($sidBinary));
}
public function testConvertSID2StrInputError() {
list($lw, $con, $um) = $this->getConnecterAndLdapMock();
$access = new Access($con, $lw, $um);
if(!function_exists('\bcadd')) {
$this->markTestSkipped('bcmath not available');
}
$sidIllegal = 'foobar';
$sidExpected = '';
$this->assertSame($sidExpected, $access->convertSID2Str($sidIllegal));
}
public function testConvertSID2StrNoBCMath() {
if(function_exists('\bcadd')) {
$removed = false;
if(function_exists('runkit_function_remove')) {
$removed = !runkit_function_remove('\bcadd');
}
if(!$removed) {
$this->markTestSkipped('bcadd could not be removed for ' .
'testing without bcmath');
}
}
list($lw, $con, $um) = $this->getConnecterAndLdapMock();
$access = new Access($con, $lw, $um);
$sidBinary = file_get_contents(__DIR__ . '/data/sid.dat');
$sidExpected = '';
$this->assertSame($sidExpected, $access->convertSID2Str($sidBinary));
}
public function testGetDomainDNFromDNSuccess() {
list($lw, $con, $um) = $this->getConnecterAndLdapMock();
$access = new Access($con, $lw, $um);
$inputDN = 'uid=zaphod,cn=foobar,dc=my,dc=server,dc=com';
$domainDN = 'dc=my,dc=server,dc=com';
$lw->expects($this->once())
->method('explodeDN')
->with($inputDN, 0)
->will($this->returnValue(explode(',', $inputDN)));
$this->assertSame($domainDN, $access->getDomainDNFromDN($inputDN));
}
public function testGetDomainDNFromDNError() {
list($lw, $con, $um) = $this->getConnecterAndLdapMock();
$access = new Access($con, $lw, $um);
$inputDN = 'foobar';
$expected = '';
$lw->expects($this->once())
->method('explodeDN')
->with($inputDN, 0)
->will($this->returnValue(false));
$this->assertSame($expected, $access->getDomainDNFromDN($inputDN));
}
}
Binary file not shown.
+151 -1
View File
@@ -95,6 +95,10 @@ class Test_Group_Ldap extends \PHPUnit_Framework_TestCase {
->method('groupname2dn')
->will($this->returnValue('cn=group,dc=foo,dc=bar'));
$access->expects($this->any())
->method('fetchListOfUsers')
->will($this->returnValue(array()));
$access->expects($this->any())
->method('readAttribute')
->will($this->returnCallback(function($name) {
@@ -111,7 +115,9 @@ class Test_Group_Ldap extends \PHPUnit_Framework_TestCase {
$access->expects($this->any())
->method('dn2username')
->will($this->returnValue('foobar'));
->will($this->returnCallback(function() {
return 'foobar' . \OCP\Util::generateRandomBytes(7);
}));
$groupBackend = new GroupLDAP($access);
$users = $groupBackend->countUsersInGroup('group', '3');
@@ -119,4 +125,148 @@ class Test_Group_Ldap extends \PHPUnit_Framework_TestCase {
$this->assertSame(2, $users);
}
public function testPrimaryGroupID2NameSuccess() {
$access = $this->getAccessMock();
$this->enableGroups($access);
$userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
$access->expects($this->once())
->method('getSID')
->with($userDN)
->will($this->returnValue('S-1-5-21-249921958-728525901-1594176202'));
$access->expects($this->once())
->method('searchGroups')
->will($this->returnValue(array('cn=foo,dc=barfoo,dc=bar')));
$access->expects($this->once())
->method('dn2groupname')
->with('cn=foo,dc=barfoo,dc=bar')
->will($this->returnValue('MyGroup'));
$groupBackend = new GroupLDAP($access);
$group = $groupBackend->primaryGroupID2Name('3117', $userDN);
$this->assertSame('MyGroup', $group);
}
public function testPrimaryGroupID2NameNoSID() {
$access = $this->getAccessMock();
$this->enableGroups($access);
$userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
$access->expects($this->once())
->method('getSID')
->with($userDN)
->will($this->returnValue(false));
$access->expects($this->never())
->method('searchGroups');
$access->expects($this->never())
->method('dn2groupname');
$groupBackend = new GroupLDAP($access);
$group = $groupBackend->primaryGroupID2Name('3117', $userDN);
$this->assertSame(false, $group);
}
public function testPrimaryGroupID2NameNoGroup() {
$access = $this->getAccessMock();
$this->enableGroups($access);
$userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
$access->expects($this->once())
->method('getSID')
->with($userDN)
->will($this->returnValue('S-1-5-21-249921958-728525901-1594176202'));
$access->expects($this->once())
->method('searchGroups')
->will($this->returnValue(array()));
$access->expects($this->never())
->method('dn2groupname');
$groupBackend = new GroupLDAP($access);
$group = $groupBackend->primaryGroupID2Name('3117', $userDN);
$this->assertSame(false, $group);
}
public function testPrimaryGroupID2NameNoName() {
$access = $this->getAccessMock();
$this->enableGroups($access);
$userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
$access->expects($this->once())
->method('getSID')
->with($userDN)
->will($this->returnValue('S-1-5-21-249921958-728525901-1594176202'));
$access->expects($this->once())
->method('searchGroups')
->will($this->returnValue(array('cn=foo,dc=barfoo,dc=bar')));
$access->expects($this->once())
->method('dn2groupname')
->will($this->returnValue(false));
$groupBackend = new GroupLDAP($access);
$group = $groupBackend->primaryGroupID2Name('3117', $userDN);
$this->assertSame(false, $group);
}
public function testGetEntryGroupIDValue() {
//tests getEntryGroupID via getGroupPrimaryGroupID
//which is basically identical to getUserPrimaryGroupIDs
$access = $this->getAccessMock();
$this->enableGroups($access);
$dn = 'cn=foobar,cn=foo,dc=barfoo,dc=bar';
$attr = 'primaryGroupToken';
$access->expects($this->once())
->method('readAttribute')
->with($dn, $attr)
->will($this->returnValue(array('3117')));
$groupBackend = new GroupLDAP($access);
$gid = $groupBackend->getGroupPrimaryGroupID($dn);
$this->assertSame('3117', $gid);
}
public function testGetEntryGroupIDNoValue() {
//tests getEntryGroupID via getGroupPrimaryGroupID
//which is basically identical to getUserPrimaryGroupIDs
$access = $this->getAccessMock();
$this->enableGroups($access);
$dn = 'cn=foobar,cn=foo,dc=barfoo,dc=bar';
$attr = 'primaryGroupToken';
$access->expects($this->once())
->method('readAttribute')
->with($dn, $attr)
->will($this->returnValue(false));
$groupBackend = new GroupLDAP($access);
$gid = $groupBackend->getGroupPrimaryGroupID($dn);
$this->assertSame(false, $gid);
}
}
+151
View File
@@ -0,0 +1,151 @@
<?php
/**
* ownCloud
*
* @author Arthur Schiwon
* @copyright 2014 Arthur Schiwon blizzz@owncloud.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\user_ldap\tests;
use OCA\user_ldap\lib\user\Manager;
class Test_User_Manager extends \PHPUnit_Framework_TestCase {
private function getTestInstances() {
$access = $this->getMock('\OCA\user_ldap\lib\user\IUserTools');
$config = $this->getMock('\OCP\IConfig');
$filesys = $this->getMock('\OCA\user_ldap\lib\FilesystemHelper');
$log = $this->getMock('\OCA\user_ldap\lib\LogWrapper');
$avaMgr = $this->getMock('\OCP\IAvatarManager');
$image = $this->getMock('\OCP\Image');
return array($access, $config, $filesys, $image, $log, $avaMgr);
}
public function testGetByDNExisting() {
list($access, $config, $filesys, $image, $log, $avaMgr) =
$this->getTestInstances();
$inputDN = 'cn=foo,dc=foobar,dc=bar';
$uid = '563418fc-423b-1033-8d1c-ad5f418ee02e';
$access->expects($this->once())
->method('dn2username')
->with($this->equalTo($inputDN))
->will($this->returnValue($uid));
$access->expects($this->never())
->method('username2dn');
$manager = new Manager($config, $filesys, $log, $avaMgr, $image);
$manager->setLdapAccess($access);
$user = $manager->get($inputDN);
$this->assertInstanceOf('\OCA\user_ldap\lib\user\User', $user);
}
public function testGetByEDirectoryDN() {
list($access, $config, $filesys, $image, $log, $avaMgr) =
$this->getTestInstances();
$inputDN = 'uid=foo,o=foobar,c=bar';
$uid = '563418fc-423b-1033-8d1c-ad5f418ee02e';
$access->expects($this->once())
->method('dn2username')
->with($this->equalTo($inputDN))
->will($this->returnValue($uid));
$access->expects($this->never())
->method('username2dn');
$manager = new Manager($config, $filesys, $log, $avaMgr, $image);
$manager->setLdapAccess($access);
$user = $manager->get($inputDN);
$this->assertInstanceOf('\OCA\user_ldap\lib\user\User', $user);
}
public function testGetByDNNotExisting() {
list($access, $config, $filesys, $image, $log, $avaMgr) =
$this->getTestInstances();
$inputDN = 'cn=gone,dc=foobar,dc=bar';
$access->expects($this->once())
->method('dn2username')
->with($this->equalTo($inputDN))
->will($this->returnValue(false));
$access->expects($this->once())
->method('username2dn')
->with($this->equalTo($inputDN))
->will($this->returnValue(false));
$manager = new Manager($config, $filesys, $log, $avaMgr, $image);
$manager->setLdapAccess($access);
$user = $manager->get($inputDN);
$this->assertNull($user);
}
public function testGetByUidExisting() {
list($access, $config, $filesys, $image, $log, $avaMgr) =
$this->getTestInstances();
$dn = 'cn=foo,dc=foobar,dc=bar';
$uid = '563418fc-423b-1033-8d1c-ad5f418ee02e';
$access->expects($this->never())
->method('dn2username');
$access->expects($this->once())
->method('username2dn')
->with($this->equalTo($uid))
->will($this->returnValue($dn));
$manager = new Manager($config, $filesys, $log, $avaMgr, $image);
$manager->setLdapAccess($access);
$user = $manager->get($uid);
$this->assertInstanceOf('\OCA\user_ldap\lib\user\User', $user);
}
public function testGetByUidNotExisting() {
list($access, $config, $filesys, $image, $log, $avaMgr) =
$this->getTestInstances();
$dn = 'cn=foo,dc=foobar,dc=bar';
$uid = 'gone';
$access->expects($this->never())
->method('dn2username');
$access->expects($this->exactly(2))
->method('username2dn')
->with($this->equalTo($uid))
->will($this->returnValue(false));
$manager = new Manager($config, $filesys, $log, $avaMgr, $image);
$manager->setLdapAccess($access);
$user = $manager->get($uid);
$this->assertNull($user);
}
}
-1
View File
@@ -73,7 +73,6 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface {
}
$user->markLogin();
$user->update();
return $user->getUsername();
}
+2 -1
View File
@@ -4,7 +4,7 @@
<name>WebDAV user backend</name>
<description>Authenticate users by a WebDAV call. You can use any WebDAV server, ownCloud server or other webserver to authenticate. It should return http 200 for right credentials and http 401 for wrong ones.
This app is not compatible with the LDAP user and group backend.</description>
Attention: This app is not compatible with the LDAP user and group backend. This app is not the webdav interface of ownCloud, if you don't understand what it does then do not enable it.</description>
<licence>AGPL</licence>
<author>Frank Karlitschek</author>
<requiremin>4.93</requiremin>
@@ -12,4 +12,5 @@
<types>
<authentication/>
</types>
<ocsid>166062</ocsid>
</info>
+1 -1
View File
@@ -1 +1 @@
1.1.0.0
1.1.0.1
+5 -5
View File
@@ -41,16 +41,16 @@ $CONFIG = array(
/* Blacklist a specific file and disallow the upload of files with this name - WARNING: USE THIS ONLY IF YOU KNOW WHAT YOU ARE DOING. */
"blacklisted_files" => array('.htaccess'),
/* The automatic hostname detection of ownCloud can fail in certain reverse proxy situations. This option allows to manually override the automatic detection. You can also add a port. For example "www.example.com:88" */
/* The automatic hostname detection of ownCloud can fail in certain reverse proxy and CLI/cron situations. This option allows to manually override the automatic detection. You can also add a port. For example "www.example.com:88" */
"overwritehost" => "",
/* The automatic protocol detection of ownCloud can fail in certain reverse proxy situations. This option allows to manually override the protocol detection. For example "https" */
/* The automatic protocol detection of ownCloud can fail in certain reverse proxy and CLI/cron situations. This option allows to manually override the protocol detection. For example "https" */
"overwriteprotocol" => "",
/* The automatic webroot detection of ownCloud can fail in certain reverse proxy situations. This option allows to manually override the automatic detection. For example "/domain.tld/ownCloud". The value "/" can be used to remove the root. */
/* The automatic webroot detection of ownCloud can fail in certain reverse proxy and CLI/cron situations. This option allows to manually override the automatic detection. For example "/domain.tld/ownCloud". The value "/" can be used to remove the root. */
"overwritewebroot" => "",
/* The automatic detection of ownCloud can fail in certain reverse proxy situations. This option allows to define a manually override condition as regular expression for the remote ip address. For example "^10\.0\.0\.[1-3]$" */
/* The automatic detection of ownCloud can fail in certain reverse proxy and CLI/cron situations. This option allows to define a manually override condition as regular expression for the remote ip address. For example "^10\.0\.0\.[1-3]$" */
"overwritecondaddr" => "",
/* A proxy to use to connect to the internet. For example "myproxy.org:88" */
@@ -88,7 +88,7 @@ $CONFIG = array(
"appstoreenabled" => true,
/* URL of the appstore to use, server should understand OCS */
"appstoreurl" => "http://api.apps.owncloud.com/v1",
"appstoreurl" => "https://api.owncloud.com/v1",
/* Domain name used by ownCloud for the sender mail address, e.g. no-reply@example.com */
"mail_domain" => "example.com",
+6
View File
@@ -241,20 +241,26 @@
float: left;
display: inline-block;
margin-right: 5px;
cursor: pointer;
}
#header .avatardiv img {
opacity: 1;
cursor: pointer;
}
#settings {
float: right;
color: #bbb;
cursor: pointer;
}
#expand {
display: block;
padding: 7px 12px 6px 7px;
cursor: pointer;
}
#expand * {
cursor: pointer;
}
#expand:hover, #expand:focus, #expand:active { color:#fff; }
#expand img { -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=70)"; filter:alpha(opacity=70); opacity:.7; margin-bottom:-2px; }
#expand:hover img, #expand:focus img, #expand:active img { -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; filter:alpha(opacity=100); opacity:1; }
+11
View File
@@ -31,6 +31,17 @@
margin-top: 10px;
width: 100%;
}
/* align primary button to right, other buttons to left */
.oc-dialog-buttonrow.twobuttons button:nth-child(1) {
float: left;
}
.oc-dialog-buttonrow.twobuttons button:nth-child(2) {
float: right;
}
.oc-dialog-buttonrow.onebutton button {
float: right;
}
.oc-dialog-close {
position:absolute;
+14 -3
View File
@@ -420,13 +420,13 @@ input[name='password-clone'] {
#password-icon {
position: absolute;
left: 16px;
top: 20px;
top: 22px;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)";
filter: alpha(opacity=30);
opacity: .3;
}
#adminpass-icon, #password-icon {
top: 15px;
top: 17px;
}
/* General new input field look */
@@ -474,10 +474,10 @@ label.infield {
#body-login form input[type="checkbox"]+label {
position: relative;
margin: 0;
font-size: 13px;
padding: 14px;
padding-left: 28px;
margin-left: -28px;
vertical-align: middle;
}
#body-login form .errors { background:#fed7d7; border:1px solid #f00; list-style-indent:inside; margin:0 0 2em; padding:1em; }
#body-login .success { background:#d7fed7; border:1px solid #0f0; width: 35%; margin: 30px auto; padding:1em; text-align: center;}
@@ -812,6 +812,7 @@ div.crumb {
display: block;
background: url('../img/breadcrumb.svg') no-repeat right center;
height: 44px;
background-size: auto 24px;
}
div.crumb.hidden {
display: none;
@@ -853,6 +854,16 @@ div.crumb:active {
opacity: 0;
}
/* LEGACY FIX only - do not use fieldsets for settings */
fieldset.warning legend, fieldset.update legend {
top: 18px;
position: relative;
}
fieldset.warning legend + p, fieldset.update legend + p {
margin-top: 12px;
}
/* for IE10 */
@-ms-viewport {
width: device-width;
+7
View File
@@ -111,6 +111,13 @@
var $buttonrow = $('<div class="oc-dialog-buttonrow" />');
this.$buttonrow = $buttonrow.appendTo(this.$dialog);
}
if (value.length === 1) {
this.$buttonrow.addClass('onebutton');
} else if (value.length === 2) {
this.$buttonrow.addClass('twobuttons');
} else if (value.length === 3) {
this.$buttonrow.addClass('threebuttons');
}
$.each(value, function(idx, val) {
var $button = $('<button>').text(val.text);
if (val.classes) {
+5 -3
View File
@@ -380,7 +380,7 @@ var OC={
* Do a search query and display the results
* @param {string} query the search query
*/
search:function(query){
search: _.debounce(function(query){
if(query){
OC.addStyle('search','results');
$.getJSON(OC.filePath('search','ajax','search.php')+'?query='+encodeURIComponent(query), function(results){
@@ -388,7 +388,7 @@ var OC={
OC.search.showResults(results);
});
}
},
}, 500),
dialogs:OCdialogs,
mtime2date:function(mtime) {
mtime = parseInt(mtime,10);
@@ -1138,7 +1138,9 @@ function initCore() {
if(!$app.is('a')) {
$app = $app.closest('a');
}
$app.addClass('app-loading');
if(!event.ctrlKey) {
$app.addClass('app-loading');
}
});
}
+22 -24
View File
@@ -19,7 +19,7 @@
*
*/
/* global OC, t, alert, $ */
/* global alert */
/**
* this class to ease the usage of jquery dialogs
@@ -66,7 +66,7 @@ var OCdialogs = {
* @param modal make the dialog modal
*/
confirm:function(text, title, callback, modal) {
this.message(
return this.message(
text,
title,
'notice',
@@ -86,7 +86,7 @@ var OCdialogs = {
* @param password whether the input should be a password input
*/
prompt: function (text, title, callback, modal, name, password) {
$.when(this._getMessageTemplate()).then(function ($tmpl) {
return $.when(this._getMessageTemplate()).then(function ($tmpl) {
var dialogName = 'oc-dialog-' + OCdialogs.dialogsCounter + '-content';
var dialogId = '#' + dialogName;
var $dlg = $tmpl.octemplate({
@@ -104,8 +104,15 @@ var OCdialogs = {
modal = false;
}
$('body').append($dlg);
var buttonlist = [
{
var buttonlist = [{
text : t('core', 'No'),
click: function () {
if (callback !== undefined) {
callback(false, input.val());
}
$(dialogId).ocdialog('close');
}
}, {
text : t('core', 'Yes'),
click : function () {
if (callback !== undefined) {
@@ -114,15 +121,6 @@ var OCdialogs = {
$(dialogId).ocdialog('close');
},
defaultButton: true
},
{
text : t('core', 'No'),
click: function () {
if (callback !== undefined) {
callback(false, input.val());
}
$(dialogId).ocdialog('close');
}
}
];
@@ -237,7 +235,7 @@ var OCdialogs = {
* You better use a wrapper instead ...
*/
message:function(content, title, dialogType, buttons, callback, modal) {
$.when(this._getMessageTemplate()).then(function($tmpl) {
return $.when(this._getMessageTemplate()).then(function($tmpl) {
var dialogName = 'oc-dialog-' + OCdialogs.dialogsCounter + '-content';
var dialogId = '#' + dialogName;
var $dlg = $tmpl.octemplate({
@@ -254,6 +252,15 @@ var OCdialogs = {
switch (buttons) {
case OCdialogs.YES_NO_BUTTONS:
buttonlist = [{
text: t('core', 'No'),
click: function(){
if (callback !== undefined) {
callback(false);
}
$(dialogId).ocdialog('close');
}
},
{
text: t('core', 'Yes'),
click: function(){
if (callback !== undefined) {
@@ -262,15 +269,6 @@ var OCdialogs = {
$(dialogId).ocdialog('close');
},
defaultButton: true
},
{
text: t('core', 'No'),
click: function(){
if (callback !== undefined) {
callback(false);
}
$(dialogId).ocdialog('close');
}
}];
break;
case OCdialogs.OK_BUTTON:
+4
View File
@@ -52,6 +52,10 @@ $(document).ready(function() {
$(':submit', this).attr('disabled','disabled').val($(':submit', this).data('finishing'));
$('input', this).addClass('ui-state-disabled').attr('disabled','disabled');
$('#selectDbType').buttonset('disable');
$('.strengthify-wrapper, .tipsy')
.css('-ms-filter', '"progid:DXImageTransform.Microsoft.Alpha(Opacity=30)"')
.css('filter', 'alpha(opacity=30)')
.css('opacity', .3);
// Create the form
var form = $('<form>');
+2 -2
View File
@@ -191,7 +191,7 @@ OC.Share={
var parts = this._REMOTE_OWNER_REGEXP.exec(owner);
if (!parts) {
// display as is, most likely to be a simple owner name
return t('files_sharing', 'Shared by {owner}', {owner: escapeHTML(owner)});
return escapeHTML(owner);
}
var userName = parts[1];
@@ -211,7 +211,7 @@ OC.Share={
html += '<span class="userDomain">@' + escapeHTML(userDomain) + '</span>';
}
html += '</span>';
return t('files_sharing', 'Shared by {owner}', {owner: html});
return html;
},
/**
* Marks/unmarks a given file as shared by changing its action icon
+760 -543
View File
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -403,39 +403,39 @@ describe('OC.Share tests', function() {
}
it('displays the local share owner as is', function() {
checkOwner('User One', 'Shared by User One', null);
checkOwner('User One', 'User One', null);
});
it('displays the user name part of a remote share owner', function() {
checkOwner(
'User One@someserver.com',
'Shared by User One',
'User One',
'User One@someserver.com'
);
checkOwner(
'User One@someserver.com/',
'Shared by User One',
'User One',
'User One@someserver.com'
);
checkOwner(
'User One@someserver.com/root/of/owncloud',
'Shared by User One',
'User One',
'User One@someserver.com'
);
});
it('displays the user name part with domain of a remote share owner', function() {
checkOwner(
'User One@example.com@someserver.com',
'Shared by User One@example.com',
'User One@example.com',
'User One@example.com@someserver.com'
);
checkOwner(
'User One@example.com@someserver.com/',
'Shared by User One@example.com',
'User One@example.com',
'User One@example.com@someserver.com'
);
checkOwner(
'User One@example.com@someserver.com/root/of/owncloud',
'Shared by User One@example.com',
'User One@example.com',
'User One@example.com@someserver.com'
);
});
+1 -1
View File
@@ -41,7 +41,7 @@ class Controller {
'dbpass' => '',
'dbname' => '',
'dbtablespace' => '',
'dbhost' => '',
'dbhost' => 'localhost',
'dbtype' => '',
);
$parameters = array_merge($defaults, $post);
+1
View File
@@ -113,6 +113,7 @@
<a href="<?php print_unescaped(OC_Helper::linkToRoute('settings_apps').'?installed'); ?>" title=""
<?php if( $_['appsmanagement_active'] ): ?> class="active"<?php endif; ?>>
<img class="app-icon svg" alt="" src="<?php print_unescaped(OC_Helper::imagePath('settings', 'apps.svg')); ?>"/>
<div class="icon-loading-dark" style="display:none;"></div>
<span>
<?php p($l->t('Apps')); ?>
</span>
-12
View File
@@ -942,18 +942,6 @@
<sorting>ascending</sorting>
</field>
</index>
<index>
<name>file_target_index</name>
<field>
<name>file_target</name>
</field>
<field>
<name>uid_owner</name>
</field>
<field>
<name>share_type</name>
</field>
</index>
</declaration>
</table>
+1 -1
View File
@@ -557,7 +557,7 @@ msgid ""
msgstr ""
#: templates/admin.php:229
msgid "Use systems cron service to call the cron.php file every 15 minutes."
msgid "Use system's cron service to call the cron.php file every 15 minutes."
msgstr ""
#: templates/admin.php:234
+1 -1
View File
@@ -557,7 +557,7 @@ msgid ""
msgstr ""
#: templates/admin.php:229
msgid "Use systems cron service to call the cron.php file every 15 minutes."
msgid "Use system's cron service to call the cron.php file every 15 minutes."
msgstr ""
#: templates/admin.php:234
+1 -1
View File
@@ -467,7 +467,7 @@ msgid ""
msgstr ""
#: templates/admin.php:200
msgid "Use systems cron service to call the cron.php file every 15 minutes."
msgid "Use system's cron service to call the cron.php file every 15 minutes."
msgstr ""
#: templates/admin.php:205
+1 -1
View File
@@ -557,7 +557,7 @@ msgid ""
msgstr ""
#: templates/admin.php:229
msgid "Use systems cron service to call the cron.php file every 15 minutes."
msgid "Use system's cron service to call the cron.php file every 15 minutes."
msgstr ""
#: templates/admin.php:234
+1 -1
View File
@@ -557,7 +557,7 @@ msgid ""
msgstr ""
#: templates/admin.php:229
msgid "Use systems cron service to call the cron.php file every 15 minutes."
msgid "Use system's cron service to call the cron.php file every 15 minutes."
msgstr ""
#: templates/admin.php:234
+1 -1
View File
@@ -557,7 +557,7 @@ msgid ""
msgstr ""
#: templates/admin.php:229
msgid "Use systems cron service to call the cron.php file every 15 minutes."
msgid "Use system's cron service to call the cron.php file every 15 minutes."
msgstr ""
#: templates/admin.php:234
+1 -1
View File
@@ -560,7 +560,7 @@ msgid ""
msgstr "الملف cron.php تم تسجيله فى خدمه webcron لاستدعاء الملف cron.php كل 15 دقيقه"
#: templates/admin.php:229
msgid "Use systems cron service to call the cron.php file every 15 minutes."
msgid "Use system's cron service to call the cron.php file every 15 minutes."
msgstr "استخدم نظام خدمة cron لـ استدعاء ملف cron.php كل 15 دقيقة "
#: templates/admin.php:234
+1 -1
View File
@@ -559,7 +559,7 @@ msgid ""
msgstr "cron.php rexístrase nun serviciu webcron pa llamar a cron.php cada 15 minutos al traviés de HTTP."
#: templates/admin.php:229
msgid "Use systems cron service to call the cron.php file every 15 minutes."
msgid "Use system's cron service to call the cron.php file every 15 minutes."
msgstr "Usa'l serviciu cron del sistema pa llamar al ficheru cron.php cada 15 minutos."
#: templates/admin.php:234

Some files were not shown because too many files have changed in this diff Show More