Compare commits

...

333 Commits

Author SHA1 Message Date
Frank Karlitschek a3dcbe5bce 7.0.2 2014-08-26 12:03:37 +02:00
Lukas Reschke dac8dead07 Redirect user after clicking on link
Use DI
2014-08-25 22:15:54 +02:00
Lukas Reschke 09ab1f16c0 Expose setSystemValue 2014-08-25 22:15:54 +02:00
Lukas Reschke 0a2e471163 Add a trusted domain wizard
Adds a little button to the trusted domain warning, if an admin clicks on the warning he will be redirected to ownCloud and asked whether he want to trust this domain.

By far not the cleanest code, or clean at all, but does the job and I don't see a reason to make a lot of changes for this little improvement.
2014-08-25 22:15:54 +02:00
Vincent Petry a70fe184e7 Do not load apps when an upgrade is due
This makes it still possible to update from the command line, but
disables custom commands from apps
2014-08-25 16:21:03 +02:00
kondou e552573bed Warn user on resetting passwords via occ when encryption is enabled 2014-08-25 15:30:55 +02:00
Robin Appelman ffdcab6cd5 Stream downloads from Swift object stores without downloading it first 2014-08-25 13:24:25 +02:00
Vincent Petry c38fcb520a Fix share dropdown when links are not allowed
When links are not allowed, the email field does not exist and
autocomplete returns null. This causes Javascript errors.

The fix prevents entering the bogus block when links aren't allowed, as
it doesn't make sense to enter it in such cases anyway.

Backport of 98d06094e7 from master
2014-08-21 15:31:43 +02:00
Vincent Petry 6c1e19b386 Do not close container/slider when clicking on single select field 2014-08-21 08:40:26 +02:00
Arthur Schiwon 2307a5a6b5 it does not affect gravity on planet earth, but only for tipsy 2014-08-21 08:40:17 +02:00
Arthur Schiwon a3a97cf1f5 make singleselect check for gravity wish, and make it south for default quota 2014-08-21 08:40:09 +02:00
Morris Jobke 4e4b299ceb replace spaces with tabs in apps.js 2014-08-21 08:39:59 +02:00
Vincent Petry e9497182ba Fix default quota settings field
The default quota settings field is initially hidden which makes it
impossible for singleSelect() to make its width measurements.

This fix uses the app navigation slide "show" event to defer the
singleSelect() initialization on the default quota field.

Refactored setQuota() into UserList._updateQuota().
Refactored duplicate event handler code into UserList.onQuotaSelect().
2014-08-21 08:39:01 +02:00
Vincent Petry 02ac579eae Use global apps slide toggle logic
Remove local app settings slide logic and make it use the global one
triggered by the "data-apps-slide-toggle" attribute.
2014-08-21 08:38:55 +02:00
Vincent Petry b94c88266b Trigger events when app-settings visibility changes 2014-08-21 08:38:49 +02:00
Frank Karlitschek 1f52e975fa 7.0.2 RC1 2014-08-21 05:36:33 +02:00
Jörn Friedrich Dreyer a3c6d20ccb use S3Client::encodeKey(), fixes #8325 2014-08-20 22:27:44 +02:00
Jörn Friedrich Dreyer 9caff0be96 add unit test for #8325 2014-08-20 22:27:40 +02:00
Vincent Petry c6e87acb96 Return whole file if range request cannot be granted due to encryption
Whenenver range headers are set and encryption is enabled, it is not
possible to automatically fseek() to the proper position.

To avoid returning corrupt/invalid data or causing a decryption error,
the range headers are stripped so that the SabreDAV code in httpGet()
returns the whole file.

Backport of cc8c1d8e07 from master
2014-08-20 21:56:09 +02:00
Arthur Schiwon 899035bfd3 backport of #10340
better check whether string resembles a DN, fixes #9887

adjust login test to code changes

unify tests
2014-08-20 17:45:04 +02:00
Joas Schilling e311535ae1 Ensure that filename is prefixed with a slash 2014-08-20 11:18:46 +02:00
Joas Schilling b1b745c052 Do not filemtime() on "." directory. Use empty string instead
Fix #9928
2014-08-20 11:18:43 +02:00
Joas Schilling a9ce0edecb Remove doubled slash between folder and path 2014-08-20 11:18:40 +02:00
Jan-Christoph Borchardt dce2e5e3b7 remove obsolete 'Download preparing' message for zip downloads, fix #3755 2014-08-19 22:53:17 +02:00
Jan-Christoph Borchardt 80d3f30ada fix multiselect bar offset on shared page 2014-08-19 17:12:07 +02:00
Robin Appelman 7842014a68 fix undefined variable 2014-08-19 14:05:26 +02:00
Robin Appelman 2facba644e only set core version at the end 2014-08-19 14:05:19 +02:00
Robin Appelman 75236b0ee6 Set maintaince mode when updating an app from the app store 2014-08-19 14:05:06 +02:00
Robin Appelman 4d2ab79392 Also set the app version when updating from app store 2014-08-19 14:04:53 +02:00
Robin Appelman 322cd65b9e Send feedback from upgrading apps to the upgrade ui 2014-08-19 14:04:44 +02:00
Robin Appelman c47a32d515 Allow loading app without checking the upgrade 2014-08-19 14:04:31 +02:00
Robin Appelman d9008f8ae4 extract upgrade parts to their own methods 2014-08-19 14:04:24 +02:00
Robin Appelman 22387b8346 handle service not available exceptions in index, remote and public.php 2014-08-19 14:04:13 +02:00
Robin Appelman 9173a661bd Throw an exception when we try to load an app that needs to be upgraded 2014-08-19 14:04:04 +02:00
Robin Appelman c69215c115 Extend OC_Util::needUpgrade to also catch app upgrades 2014-08-19 14:03:50 +02:00
Vincent Petry efca0ab4d4 Hide sharing sections in files app when sharing API is disabled
Backport of 1de5ae8845 from master
2014-08-19 12:52:32 +02:00
Jörn Friedrich Dreyer c4fd3dbd2a properly encode groups as json, not ',' separated 2014-08-19 11:21:04 +02:00
Lukas Reschke 67399901dc Fix copyright 2014-08-19 10:21:50 +02:00
Bjoern Schiessle 23f4e7c1cb fix broken unit tests 2014-08-19 10:21:37 +02:00
Bjoern Schiessle 29e9cb51dc update unit tests with configurable share folder 2014-08-19 10:21:26 +02:00
Bjoern Schiessle 1519f018a4 make share folder configurable 2014-08-19 10:21:16 +02:00
Bjoern Schiessle 2b449dd4fd set incognitoMode to true, getUser should always return false during public upload 2014-08-19 10:13:24 +02:00
Jan-Christoph Borchardt be7cb7298a fix color of links in warnings, fix #10480 2014-08-18 17:18:24 +02:00
Joas Schilling 86009a564e Add a note about overwritewebroot when using system cron
If the current webroot is non-empty but the webroot from the config is,
and system cron is used, the URL generator fails to build valid URLs.
So we notify the admin to set it up correctly.

Fix #9995
2014-08-18 16:32:16 +02:00
Vincent Petry 9d5d0cca7a Added upgrade notice to avoid timeouts 2014-08-18 16:04:39 +02:00
Vincent Petry e0c62bbd64 Fixed folder icon update routine when share owner exists
Whenever a folder has a "data-share-owner" attribute, the icon is now
properly updated to a shared folder icon.

Backport of 607ea636be from master
2014-08-18 14:55:36 +02:00
Georg Ehrke f82467f845 check if array index ocsid is set before accessing it 2014-08-18 10:45:44 +02:00
Stefan Rado cac56279c2 Make skeleton directory configurable. 2014-08-18 09:46:17 +02:00
Lukas Reschke 5e8733a9f6 Fix unit test 2014-08-15 16:52:17 +02:00
Lukas Reschke e7cea79ee7 Move authentication failed logging to checkPassword
Fixes https://github.com/owncloud/core/issues/10366
2014-08-15 16:52:13 +02:00
Clark Tomlinson 1a2bae347a Merge pull request #10440 from owncloud/enforce-debug
Remove ability to trigger DEBUG mode via cookie
2014-08-15 09:13:27 -04:00
Jan-Christoph Borchardt ed6365af4a change error text color to white for better contrast, fix #10424 2014-08-15 14:21:58 +02:00
Oliver Gasser 8cd5e652de Compare upload limit against biggest file
When uploading multiple files from the web interface, compare the PHP
upload limit against the largest file, not against the sum of all files.
2014-08-15 09:42:40 +02:00
pzy 5dd950bc0a Update public.php
added tag to make facebook load a preview picture

Update public.php

add check ifMimeSupported and put the thumbsize in a variable

generate preview for all supported mimes
2014-08-15 09:17:35 +02:00
Thomas Müller e3cf107e1d in order to prevent false-positives on the code checker - exec and eval will not longer be grepped for 2014-08-14 23:20:50 +02:00
Bjoern Schiessle 4c6c22c4e3 we need the recipient as a additional parameter to know for which share the notification was send 2014-08-14 17:45:18 +02:00
Vincent Petry 35c133143a Fix issue when no apps are enabled
Properly initialize $apps array
2014-08-14 13:52:31 +02:00
Bjoern Schiessle 34e0259fa8 new unit test added 2014-08-14 11:45:30 +02:00
Bjoern Schiessle fe119300c7 no error if we try to delete a file which no longer exists 2014-08-14 11:45:13 +02:00
Bjoern Schiessle 9a83dbec76 no special action for folder named 'Shared' needed 2014-08-14 11:44:59 +02:00
Bjoern Schiessle bc1848933d add error message if user wants to rename a file which no longer exists 2014-08-14 11:44:44 +02:00
Robin Appelman e2f23b24f7 Cast file id's to int so we can compare them properly 2014-08-14 00:55:10 +02:00
Jan-Christoph Borchardt 4593d9f819 trigger lazy loading earlier, fix #9823 2014-08-14 00:54:04 +02:00
Thomas Müller 0711ab5de7 Merge pull request #10406 from owncloud/japanese-stable7
fix japanese lang code
2014-08-13 23:57:14 +02:00
Volkan Gezer 1266ebdb10 fix japanese lang code 2014-08-13 22:29:56 +02:00
Volkan Gezer 1b4793db66 Typo fix NL 2014-08-13 22:23:50 +02:00
gekmihesg eb68b0d5c6 Load authentication backends before tryBasicAuth 2014-08-13 16:09:59 +02:00
Bjoern Schiessle ca3d09c940 add unit test for aes256/aes128 2014-08-13 13:06:29 +02:00
Bjoern Schiessle f979a0c09d update existing unit tests 2014-08-13 13:06:15 +02:00
Bjoern Schiessle 09ed6b5ad4 support aes 256 2014-08-13 13:05:41 +02:00
Jörn Friedrich Dreyer 113049b8f2 Merge pull request #10365 from owncloud/check_quota_on_new_via_web_stable7
check quota when trying to download a file via new -> web
2014-08-13 11:54:02 +02:00
Jörn Friedrich Dreyer c8f81e6241 check quota when trying to download a file via new -> web 2014-08-13 10:44:52 +02:00
Bjoern Schiessle 7aa35e9f24 fix broken variable name, recoveryPasswordSupported is now recoveryEnabledForUser 2014-08-12 19:27:09 +02:00
Bjoern Schiessle 9f9c18bd74 add unit tests 2014-08-12 17:54:54 +02:00
Bjoern Schiessle 6af1a2d914 fix detection of system wide mount points 2014-08-12 17:52:40 +02:00
Jan-Christoph Borchardt 3432b2e386 Merge pull request #10271 from miicha/patch-2
Fix display of checkboxes in Pale Moon
2014-08-12 13:00:05 +02:00
Bjoern Schiessle 5b27b01525 remove 'no people found' entry 2014-08-12 10:02:01 +02:00
Tom Needham 3bcca4f344 Add scrollto to the url if sharing a file for long file lists 2014-08-11 18:25:01 +01:00
Tom Needham 8972f74ecf Fix link to files and folders in internal share emails 2014-08-11 18:25:01 +01:00
Thomas Müller 1051460afc in case $_POST['itemSourceName'] does not exist we simply default it to null 2014-08-11 17:42:37 +02:00
Jean-Louis Dupond 6907eb3eb0 Backport of #9848
Fix memberOf detection. Fixes: #9835

Fix remarks in #9848

Fix initializing in #9848
2014-08-11 17:00:38 +02:00
Thomas Müller d1658c1d2c shared files/folders are not mounted 2014-08-11 14:42:56 +02:00
miicha cb0cdd1d74 remove obsolete code comment 2014-08-11 13:36:11 +02:00
Bjoern Schiessle 5c89d9c9ee update unit test, min date should be always today + 1 2014-08-11 10:07:41 +02:00
Bjoern Schiessle 493b724935 set minDate always to today + one day 2014-08-11 10:07:30 +02:00
Jan-Christoph Borchardt b50a4e918b simplify app navigation look
* remove superfluous border as it is distinguished enough via background color
* remove superfluous border from settings entry
* fix settings entry width
2014-08-11 08:46:17 +02:00
Robin Appelman db7b245800 Add group management to the public api 2014-08-10 20:58:31 +02:00
Jan-Christoph Borchardt b3e2f6b457 remove confusing 'automatic logon rejected' message, fix #8591 2014-08-09 22:06:08 +02:00
Thomas Müller db117bd468 Merge pull request #10287 from owncloud/fix_search_in_shared_files_stable7
Fix search in shared files stable7
2014-08-08 17:37:21 +02:00
Bjoern Schiessle 8c892638f5 remove share permissions if user is excluded from sharing 2014-08-08 14:01:49 +02:00
Bjoern Schiessle a8f24ed408 only show "share with others" and "share by link" navigation bar entries if user is allowed to share files 2014-08-08 14:01:37 +02:00
Jörn Friedrich Dreyer 41fe3f2652 add test for search() in shared cache 2014-08-08 11:47:56 +02:00
Jörn Friedrich Dreyer 592a2dc82f traverse folders in php to search in shared files 2014-08-08 11:47:47 +02:00
Bjoern Schiessle 18c023da16 add logout hook to remove keys from session 2014-08-08 09:36:48 +02:00
Georg Ehrke 68ba31fd4c implement a txt preview fallback for the case that ttf is not support 2014-08-07 22:59:25 +02:00
Arthur Schiwon 0c057bf310 ldap_ prefix will be added in invokeLDAPMethod(), having it would lead to a unexisting function, fixes #9829 2014-08-07 21:41:30 +02:00
miicha 4748923dbc Fix display of checkboxes in Pale Moon
In Pale Moon (24.7.1) the checkboxes in the file list are not in the responding line on the icon but all of them are next to the "select all" checkbox.
This fix should not change anything in Firefox and chromium (on windows) but as the comment states it has to be checked on KDE/Qt.
BTW why is position absolute naccessary? For my understanding relative is exactly what you want to use in such a case...
2014-08-07 19:33:44 +02:00
Tom Needham 44ae0868fd Use human readable relative date for oc-dialog-filepickers 2014-08-07 15:17:51 +01:00
Thomas Müller 8f8923a714 we can only close the session if encryption is not used 2014-08-07 14:32:18 +02:00
Dan Jones 9ba8f5b604 fix tiny thumbnails in public preview/share mode 2014-08-07 14:31:14 +02:00
Robin Appelman 2213127094 trim leading slash 2014-08-07 13:44:49 +02:00
Robin Appelman 9598efc7cd Fix SharedCache::getPathById 2014-08-07 13:44:47 +02:00
Robin Appelman 8c56be5722 return null instead of throwing an exception 2014-08-07 13:42:25 +02:00
Robin Appelman 34963b0d89 Fix Folder::getById 2014-08-07 13:42:17 +02:00
Thomas Müller 2b9c093510 Merge pull request #10226 from owncloud/fix-lowlat-cache-autoload-stable7
Fix lowlat cache autoload stable7
2014-08-07 10:30:13 +02:00
Bernhard Posselt 3c68defac2 use id instead of resourceId 2014-08-07 10:19:07 +02:00
Bernhard Posselt 8417f55b01 dont strip the s from the resource 2014-08-07 10:19:00 +02:00
Jörn Friedrich Dreyer 1ca1e1d4d1 fix return documentation 2014-08-07 00:55:22 +02:00
Thomas Müller 5b88d3d3fc prevent PHP errors and enhance logging 2014-08-07 00:55:22 +02:00
Andreas Fischer 12c7ddc18f Add registerAutoloaderCache(). 2014-08-06 21:40:48 +02:00
Andreas Fischer cc1d95cbe5 Memcache\Factory: Remove static, use globalPrefix. 2014-08-06 21:40:38 +02:00
Andreas Fischer ef6a0254f9 InstanceId is properly injected into factory. Remove comment. 2014-08-06 21:40:22 +02:00
Thomas Müller d59b94fa4c Merge pull request #10204 from owncloud/backport-10144-stable7
Backport of #10144 to stable7
2014-08-06 15:44:52 +02:00
Bjoern Schiessle 079390c037 remove trailing slash from path 2014-08-06 15:19:10 +02:00
Bjoern Schiessle 8a038eeeef remove unused variable 2014-08-06 15:18:57 +02:00
Bjoern Schiessle 7540a58ce7 add unit test to make sure getVersions returns the correct result 2014-08-06 10:43:51 +02:00
Bjoern Schiessle 97a62f6bc4 make sure that the versions array contains the correct path 2014-08-06 10:43:38 +02:00
Joas Schilling cc531fc905 Fix isLoggedIn() check for user '0'
Fix #9972

Conflicts:
	lib/private/user/session.php
2014-08-06 10:24:05 +02:00
Joas Schilling 2e04c5e956 Fix getting group '0' from database backend
Fix #9972
2014-08-06 09:55:11 +02:00
scolebrook ec79e5470a Add ability to theme iOS and Android client URLs just like desktop URLs.
* added to personal settings page.
* fix uppercase issue
* remove escaping because it's unneeded
2014-08-06 08:56:31 +02:00
Jan-Christoph Borchardt 9b9fbc60c0 use correct app-icon class for new apps as well, fix icon size 2014-08-06 08:35:56 +02:00
Bjoern Schiessle a1656abb4c don't display share permission if resharing was disabled by the admin 2014-08-05 13:46:23 +00:00
Thomas Müller 8f8f3d1e43 close session right before the download starts - this enables parallel downloads 2014-08-05 14:55:07 +02:00
Arthur Schiwon 3ebdb3593a Hack to avoid Agent DN + Password being overwritten by some ugly browsers with stored site credentials
Conflicts:
	apps/user_ldap/css/settings.css
2014-08-05 12:17:36 +02:00
Jan-Christoph Borchardt 5bc562d2ce fix long filename display in filepicker 2014-08-05 07:47:25 +02:00
Jan-Christoph Borchardt 039b1e3715 also fix filepicker for smaller screen sizes 2014-08-05 07:47:08 +02:00
Jan-Christoph Borchardt b21a1de07a fix date display in filepicker 2014-08-05 07:47:08 +02:00
Remco Brenninkmeijer aba52ff02f Quick fix for #9119. Increase preview to keep empty message more centered and push footer down. 2014-08-05 07:46:27 +02:00
Robin Appelman 3e103f5bee Dont touch non-oc tables when doing the InnoDB repair step 2014-08-04 17:10:18 +02:00
Frank Karlitschek 5a57d2bd42 7.0.1 2014-08-04 15:43:20 +02:00
Morris Jobke ed89a746e2 Merge pull request #10122 from malditoastur/patch-1
Update ast.php
2014-08-04 11:07:57 +02:00
malditoastur 78af01393f Update ast.php
Following the thread in:

https://github.com/owncloud/core/issues/10112

I modify this file hoping to be included in next ownCloud 7.0.x version. (Please, I don't have enough github knowledgment. Sorry if i'm doing something wrong..)
2014-08-02 21:06:13 +02:00
Thomas Müller f359e3432e only if the environment variable RUN_OBJECTSTORE_TESTS is set the object store unit test will be executed 2014-07-31 22:43:45 +02:00
Frank Karlitschek c0f0e79b5e Merge branch 'stable7' of https://github.com/owncloud/core into stable7 2014-07-30 17:40:05 -04:00
Frank Karlitschek 727374a6f3 7.0.1 RC1 2014-07-30 17:39:38 -04:00
Jan-Christoph Borchardt a0f024fec2 set max width for notifications so they won't overlap the whole header 2014-07-30 21:30:08 +02:00
Jan-Christoph Borchardt 32cec6cae4 fix yellow notification style 2014-07-30 21:29:54 +02:00
Jan-Christoph Borchardt 57c954a345 fix notification preventing top bar clickability, fix #9680 2014-07-30 21:29:45 +02:00
Georg Ehrke 24c8774b7f don't preload videos on public sharing, fixes #10042 2014-07-30 21:11:23 +02:00
Georg Ehrke f68fa072c7 extract transparency fix from #8050 2014-07-30 18:37:51 +02:00
Robin Appelman 4f40cde66a Also keep maxY into account when scaling a preview while preserving aspect ratio 2014-07-30 17:31:21 +02:00
Bjoern Schiessle 19bad71da2 make sure that we always find all versions 2014-07-30 17:21:37 +02:00
Bjoern Schiessle 2e8f993299 add unit test for rename and copy operation 2014-07-30 17:21:29 +02:00
Bjoern Schiessle 5eca22d229 make the versions and encryption app aware of the copy operation 2014-07-30 17:21:19 +02:00
Jan-Christoph Borchardt 78ea700752 Merge pull request #9902 from Der-Jan/stable7
Fixed wrong brackets in apps settings
2014-07-30 17:21:38 +02:00
Bjoern Schiessle 60b1a6e75f add unit test 2014-07-30 15:32:20 +02:00
Bjoern Schiessle 0b8135dcc4 make sure that we set the expire date if a date is adefault date is set 2014-07-30 15:32:11 +02:00
Jan-Christoph Borchardt 8f9deb118a also use link icon for the folders .. 2014-07-30 13:20:08 +02:00
Thomas Müller dc41f63930 migration test for sqlite - adding type mapping for 'tinyint unsigned' 2014-07-30 11:50:26 +02:00
Morris Jobke 185e41afdd Fix template rendering for 'blank' templates 2014-07-30 10:33:55 +02:00
Thomas Müller eb665bf3c8 only call exec() if allowed to 2014-07-29 21:39:52 +02:00
Jan-Christoph Borchardt 64094c5384 use more understandable 'link' icon for public links (instead of hard to recognize globe), fix #9707 2014-07-29 20:19:53 +02:00
Michael Göhler 1cd1214b93 max icon size for app menu 2014-07-29 16:32:48 +02:00
Bjoern Schiessle d677040bdc define type in pre hook 2014-07-29 16:28:37 +02:00
Bjoern Schiessle 72459ca50a show a warning in the personal settings and admin settins if the encyption keys are not initialized 2014-07-29 16:26:51 +02:00
Bjoern Schiessle b4a379b7e3 remove share permission if user is excluded from sharing 2014-07-29 14:05:02 +02:00
Bjoern Schiessle 59aa54ddb8 update attributes for share with user list, file should always have delete permissions, this means unshare in this context, and the overview page is always a root view 2014-07-29 14:01:06 +02:00
Thomas Müller 7287789588 Merge pull request #9990 from owncloud/fix-failing-unit-test-stable7
function declaration did not match
2014-07-29 09:17:03 +02:00
Thomas Müller bfb6b7ba90 function declaration did not match 2014-07-29 07:53:44 +02:00
Thomas Müller a50c33a91f Merge pull request #9946 from owncloud/remove-mssql-bundle-stable7
remove MssqlBundle
2014-07-28 22:44:20 +02:00
Thomas Müller 53c1e3d41b function declaration did not match 2014-07-28 22:38:56 +02:00
Thomas Müller 3b760141d6 remove MssqlBundle 2014-07-28 22:21:13 +02:00
Vincent Petry 8757a99f78 Fix enforced share expiration date to be based on share time 2014-07-28 17:58:12 +02:00
Bjoern Schiessle 663e8bc5e2 adjust error code 2014-07-28 17:28:42 +02:00
Bjoern Schiessle 2e28b7fff9 add OCS api call to set expire date for link shares 2014-07-28 17:28:34 +02:00
Thomas Müller 1831ae9c9b Merge pull request #9948 from owncloud/backport-9904-stable7
Dont throw an error when calling $server->getUserFolder when logged out
2014-07-28 17:11:00 +02:00
Thomas Müller 51cbe2a0ee generate copy of sqlite database file in data directory 2014-07-28 16:55:31 +02:00
Thomas Müller 02a61c0b6a ownCloud users are exported as address book 2014-07-28 16:35:37 +02:00
Jan-Christoph Borchardt 7cd1a48222 enable input grouping also outside of log in screen 2014-07-28 15:13:04 +02:00
Morris Jobke 0cabafb513 update getID3 library & add autoload 2014-07-28 15:06:17 +02:00
Robin Appelman df0d00c8c6 Dont try to execute jobs that no longer exist 2014-07-28 13:47:40 +02:00
Jan-Christoph Borchardt 2d7379da2c improve look of search on mobile, save space in top bar 2014-07-28 13:15:43 +02:00
Vincent Petry 9cd741417a Set version AFTER a successful update
If an app upgrade failed, the core version will not be increased either
in the database. This will re-display the update page and make it
possible to redo the apps upgrade.

Note that any core repair routine must take into account that an update
might need to be redone again even though the core's DB state is already
the one of the new version.
2014-07-28 11:21:13 +02:00
Lukas Reschke 05301825e2 Verify whether the URL is valid
Required for https://github.com/owncloud/mail/pull/100#issuecomment-50266017

@karlitschek Backport for stable6 and stable7 requested.
2014-07-28 11:08:15 +02:00
Robin Appelman 87ec3fbf1d Dont throw an error when calling $server->getUserFolder when logged out
Conflicts:
	lib/private/server.php
2014-07-28 10:08:14 +02:00
Der-Jan c72f0e692b Fixed wrong brackets in apps settings 2014-07-25 16:22:31 +02:00
Björn Schießle d28b4caa2f Merge pull request #9892 from owncloud/fix_sharing_update_oc7
fix sharing update, add proper escaping (oc7 backport)
2014-07-25 11:36:36 +02:00
Bjoern Schiessle 06bcf3db8d fix sharing update, add proper escaping 2014-07-25 10:21:18 +02:00
Vincent Petry d6e61745c8 Fix preview animation on uploading
When adding/uploading files, the preview is now animated.
When loading a list of files directly the preview is displayed directly.
2014-07-25 00:00:47 +02:00
Thomas Müller f33c49e2be - adding default value for $recoveryPassword
- set password correctly in lost password
2014-07-24 15:58:30 +02:00
Thomas Müller c152ab4572 register type mappings for unknown/unsupported mysql types 2014-07-24 15:37:49 +02:00
Thomas Müller f75f1b4412 Adding test which breaks because bit and/or enum datatypes are used 2014-07-24 15:37:49 +02:00
Vincent Petry 127aa309fb Prevent cron.php to trigger apps updating 2014-07-23 22:42:41 +02:00
Thomas Müller 303e504fcb only commit in case a transaction is active 2014-07-23 21:37:30 +02:00
Robin Appelman 3c0f5d02ba Fix remote share when remote server is installed at the root 2014-07-23 17:58:39 +02:00
Remco Brenninkmeijer faf0bfb29b Backport of sorting fix from master
Changed default sorting except for names.
Show sorting icons when hovering over
Cleanup of unnecesary addition
Fixed comments from PVince81
Corrected (Netbeans?) inserted spaces
While busy cleaning, also removed extra enters
Adjusted tests for new default sorting
Sorting triangles pointing up for ascending, down for descending

Backport + squash of 1a65d09..319caa7 from master
2014-07-23 15:54:26 +02:00
Frank Karlitschek c7d7ca455a updated the 7.0.0 release with some last minute fixes 2014-07-22 15:54:34 -04:00
Thomas Müller 555fcbdd7d Merge pull request #9777 from owncloud/fix-chunked-upload-stable7
adding special handling of checkPrecondition() for chunked upload
2014-07-22 17:25:50 +02:00
Thomas Müller 69065ceecb Merge pull request #9775 from owncloud/backport-9738-stable7
Backport 9738 stable7
2014-07-22 17:24:33 +02:00
Thomas Müller 61d9967221 adding special handling of checkPrecondition() for chunked upload 2014-07-22 15:49:20 +02:00
Lukas Reschke a393670f7d Remove uneeded strip_tags
This `strip_tags` seems to be completely unneeded and will cause problems with passwords containing stripped characters. (e.g. `<` or `>`)

Needs https://github.com/owncloud/core/pull/9735 to be merged first.
2014-07-22 15:39:34 +02:00
Andreas Fischer 9be9e777c2 Extract Auth Header logic into new function handleAuthHeaders(). 2014-07-22 15:39:33 +02:00
Andreas Fischer 0e732982ae Deduplicate user/password extraction from alternative HTTP headers. 2014-07-22 15:39:33 +02:00
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
435 changed files with 8613 additions and 2823 deletions
+2 -1
View File
@@ -26,7 +26,8 @@ $success = true;
//Now delete
foreach ($files as $file) {
if (($dir === '' && $file === 'Shared') || !\OC\Files\Filesystem::unlink($dir . '/' . $file)) {
if (\OC\Files\Filesystem::file_exists($dir . '/' . $file) &&
!\OC\Files\Filesystem::unlink($dir . '/' . $file)) {
$filesWithError .= $file . "\n";
$success = false;
}
+23
View File
@@ -108,6 +108,29 @@ if($source) {
$sourceStream=@fopen($source, 'rb', false, $ctx);
$result = 0;
if (is_resource($sourceStream)) {
$meta = stream_get_meta_data($sourceStream);
if (isset($meta['wrapper_data']) && is_array($meta['wrapper_data'])) {
//check stream size
$storageStats = \OCA\Files\Helper::buildFileStorageStatistics($dir);
$freeSpace = $storageStats['freeSpace'];
foreach($meta['wrapper_data'] as $header) {
list($name, $value) = explode(':', $header);
if ('content-length' === strtolower(trim($name))) {
$length = (int) trim($value);
if ($length > $freeSpace) {
$delta = $length - $freeSpace;
$humanDelta = OCP\Util::humanFileSize($delta);
$eventSource->send('error', array('message' => (string)$l10n->t('The file exceeds your quota by %s', array($humanDelta))));
$eventSource->close();
fclose($sourceStream);
exit();
}
}
}
}
$result=\OC\Files\Filesystem::file_put_contents($target, $sourceStream);
}
if($result) {
+3 -1
View File
@@ -24,6 +24,8 @@ if (empty($_POST['dirToken'])) {
// and the upload/file transfer code needs to be refactored into a utility method
// that could be used there
\OC_User::setIncognitoMode(true);
// return only read permissions for public upload
$allowedPermissions = OCP\PERMISSION_READ;
$publicDirectory = !empty($_POST['subdir']) ? $_POST['subdir'] : '/';
@@ -175,7 +177,7 @@ if (strpos($dir, '..') === false) {
} catch(Exception $ex) {
$error = $ex->getMessage();
}
} else {
// file already exists
$meta = \OC\Files\Filesystem::getFileInfo($target);
-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
+11 -10
View File
@@ -152,16 +152,20 @@ table th .columntitle.name {
padding-right: 80px;
margin-left: 50px;
}
/* hover effect on sortable column */
table th a.columntitle:hover {
color: #000;
}
.sort-indicator.hidden { visibility: hidden; }
table th .sort-indicator {
width: 10px;
height: 8px;
margin-left: 10px;
display: inline-block;
}
table th:hover .sort-indicator.hidden {
width: 10px;
height: 8px;
margin-left: 10px;
visibility: visible;
}
table th, table td { border-bottom:1px solid #ddd; text-align:left; font-weight:normal; }
table td {
padding: 0 15px;
@@ -345,14 +349,13 @@ table td.filename .uploadtext {
#fileList tr td.filename>input[type="checkbox"] + label,
.select-all + label {
height: 50px;
position: absolute;
position: relative;
width: 50px;
z-index: 5;
}
#fileList tr td.filename>input[type="checkbox"]{
/* sometimes checkbox height is bigger (KDE/Qt), so setting to absolute
* to prevent it to increase the height */
position: absolute;
position: relative;
z-index: 4;
}
#fileList tr td.filename>input[type="checkbox"] + label {
left: 0;
@@ -367,7 +370,6 @@ table td.filename .uploadtext {
left: 18px;
}
#fileList tr td.filename {
position: relative;
width: 100%;
@@ -432,7 +434,6 @@ a.action>img {
margin-bottom: -1px;
}
#fileList a.action {
display: inline;
padding: 18px 8px;
+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.
*
+7 -4
View File
@@ -233,7 +233,8 @@ OC.Upload = {
data.originalFiles.selection = {
uploads: [],
filesToUpload: data.originalFiles.length,
totalBytes: 0
totalBytes: 0,
biggestFileBytes: 0
};
}
var selection = data.originalFiles.selection;
@@ -273,13 +274,15 @@ OC.Upload = {
// add size
selection.totalBytes += file.size;
// update size of biggest file
selection.biggestFileBytes = Math.max(selection.biggestFileBytes, file.size);
// check PHP upload limit
if (selection.totalBytes > $('#upload_limit').val()) {
// check PHP upload limit against biggest file
if (selection.biggestFileBytes > $('#upload_limit').val()) {
data.textStatus = 'sizeexceedlimit';
data.errorThrown = t('files',
'Total file size {size1} exceeds upload limit {size2}', {
'size1': humanFileSize(selection.totalBytes),
'size1': humanFileSize(selection.biggestFileBytes),
'size2': humanFileSize($('#upload_limit').val())
});
}
+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 + '/';
}
+47 -13
View File
@@ -18,8 +18,8 @@
this.initialize($el, options);
};
FileList.prototype = {
SORT_INDICATOR_ASC_CLASS: 'icon-triangle-s',
SORT_INDICATOR_DESC_CLASS: 'icon-triangle-n',
SORT_INDICATOR_ASC_CLASS: 'icon-triangle-n',
SORT_INDICATOR_DESC_CLASS: 'icon-triangle-s',
id: 'files',
appName: t('files', 'Files'),
@@ -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);
},
/**
@@ -336,7 +338,6 @@
else {
files = _.pluck(this.getSelectedFiles(), 'name');
}
OC.Notification.show(t('files','Your download is being prepared. This might take some time if the files are big.'));
OC.redirect(this.getDownloadUrl(files, dir));
return false;
},
@@ -369,7 +370,12 @@
this.setSort(sort, (this._sortDirection === 'desc')?'asc':'desc');
}
else {
this.setSort(sort, 'asc');
if ( sort === 'name' ) { //default sorting of name is opposite to size and mtime
this.setSort(sort, 'asc');
}
else {
this.setSort(sort, 'desc');
}
}
this.reload();
}
@@ -393,7 +399,7 @@
* This appends/renders the next page of entries when reaching the bottom.
*/
_onScroll: function(e) {
if (this.$container.scrollTop() + this.$container.height() > this.$el.height() - 100) {
if (this.$container.scrollTop() + this.$container.height() > this.$el.height() - 300) {
this._nextPage(true);
}
},
@@ -598,6 +604,10 @@
"data-permissions": fileData.permissions || this.getDirectoryPermissions()
});
if (fileData.mountType) {
tr.attr('data-mounttype', fileData.mountType);
}
if (!_.isUndefined(path)) {
tr.attr('data-path', path);
}
@@ -701,6 +711,7 @@
* @param options map of attributes:
* - "updateSummary": true to update the summary after adding (default), false otherwise
* - "silent": true to prevent firing events like "fileActionsReady"
* - "animate": true to animate preview loading (defaults to true here)
* @return new tr element (not appended to the table)
*/
add: function(fileData, options) {
@@ -708,7 +719,7 @@
var $tr;
var $rows;
var $insertionPoint;
options = options || {};
options = _.extend({animate: true}, options || {});
// there are three situations to cover:
// 1) insertion point is visible on the current page
@@ -766,6 +777,7 @@
* @param options map of attributes:
* - "index" optional index at which to insert the element
* - "updateSummary" true to update the summary after adding (default), false otherwise
* - "animate" true to animate the preview rendering
* @return new tr element (not appended to the table)
*/
_renderRow: function(fileData, options) {
@@ -807,7 +819,7 @@
if (fileData.isPreviewAvailable) {
// lazy load / newly inserted td ?
if (!fileData.icon) {
if (options.animate) {
this.lazyLoadPreview({
path: path + '/' + fileData.name,
mime: mime,
@@ -908,18 +920,29 @@
this._sort = sort;
this._sortDirection = (direction === 'desc')?'desc':'asc';
this._sortComparator = comparator;
if (direction === 'desc') {
this._sortComparator = function(fileInfo1, fileInfo2) {
return -comparator(fileInfo1, fileInfo2);
};
}
this.$el.find('thead th .sort-indicator')
.removeClass(this.SORT_INDICATOR_ASC_CLASS + ' ' + this.SORT_INDICATOR_DESC_CLASS);
.removeClass(this.SORT_INDICATOR_ASC_CLASS)
.removeClass(this.SORT_INDICATOR_DESC_CLASS)
.toggleClass('hidden', true)
.addClass(this.SORT_INDICATOR_DESC_CLASS);
this.$el.find('thead th.column-' + sort + ' .sort-indicator')
.removeClass(this.SORT_INDICATOR_ASC_CLASS)
.removeClass(this.SORT_INDICATOR_DESC_CLASS)
.toggleClass('hidden', false)
.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 +968,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 +998,7 @@
}
this.setFiles(result.data.files);
return true
return true;
},
updateStorageStatistics: function(force) {
@@ -1291,6 +1321,10 @@
if (!result || result.status === 'error') {
OC.dialogs.alert(result.data.message, t('core', 'Could not rename file'));
fileInfo = oldFileInfo;
if (result.data.code === 'sourcenotfound') {
self.remove(result.data.newname, {updateSummary: true});
return;
}
}
else {
fileInfo = result.data;
@@ -1566,7 +1600,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('('));
+14 -4
View File
@@ -71,15 +71,25 @@ class App {
'data' => NULL
);
$normalizedOldPath = \OC\Files\Filesystem::normalizePath($dir . '/' . $oldname);
$normalizedNewPath = \OC\Files\Filesystem::normalizePath($dir . '/' . $newname);
// rename to non-existing folder is denied
if (!$this->view->file_exists($dir)) {
if (!$this->view->file_exists($normalizedOldPath)) {
$result['data'] = array(
'message' => $this->l10n->t('%s could not be renamed as it has been deleted', array($oldname)),
'code' => 'sourcenotfound',
'oldname' => $oldname,
'newname' => $newname,
);
}else if (!$this->view->file_exists($dir)) {
$result['data'] = array('message' => (string)$this->l10n->t(
'The target folder has been moved or deleted.',
array($dir)),
'code' => 'targetnotfound'
);
// rename to existing file is denied
} else if ($this->view->file_exists($dir . '/' . $newname)) {
} else if ($this->view->file_exists($normalizedNewPath)) {
$result['data'] = array(
'message' => $this->l10n->t(
@@ -90,10 +100,10 @@ class App {
// rename to "." is denied
$newname !== '.' and
// THEN try to rename
$this->view->rename($dir . '/' . $oldname, $dir . '/' . $newname)
$this->view->rename($normalizedOldPath, $normalizedNewPath)
) {
// successful rename
$meta = $this->view->getFileInfo($dir . '/' . $newname);
$meta = $this->view->getFileInfo($normalizedNewPath);
$fileinfo = \OCA\Files\Helper::formatFileInfo($meta);
$result['success'] = true;
$result['data'] = $fileinfo;
+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;
}
+42 -4
View File
@@ -73,10 +73,14 @@ class Test_OC_Files_App_Rename extends \PHPUnit_Framework_TestCase {
$oldname = 'oldname';
$newname = 'newname';
$this->viewMock->expects($this->at(0))
$this->viewMock->expects($this->any())
->method('file_exists')
->with('/')
->will($this->returnValue(true));
->with($this->anything())
->will($this->returnValueMap(array(
array('/', true),
array('/oldname', true)
)));
$this->viewMock->expects($this->any())
->method('getFileInfo')
@@ -119,7 +123,7 @@ class Test_OC_Files_App_Rename extends \PHPUnit_Framework_TestCase {
$this->viewMock->expects($this->at(0))
->method('file_exists')
->with('/unexist')
->with('/unexist/oldname')
->will($this->returnValue(false));
$this->viewMock->expects($this->any())
@@ -136,6 +140,40 @@ class Test_OC_Files_App_Rename extends \PHPUnit_Framework_TestCase {
$result = $this->files->rename($dir, $oldname, $newname);
$this->assertFalse($result['success']);
$this->assertEquals('sourcenotfound', $result['data']['code']);
}
/**
* Test move to a folder that doesn't exist any more
*/
function testRenameToNonExistingFolder() {
$dir = '/';
$oldname = 'oldname';
$newname = '/unexist/newname';
$this->viewMock->expects($this->any())
->method('file_exists')
->with($this->anything())
->will($this->returnValueMap(array(
array('/oldname', true),
array('/unexist', false)
)));
$this->viewMock->expects($this->any())
->method('getFileInfo')
->will($this->returnValue(array(
'fileid' => 123,
'type' => 'dir',
'mimetype' => 'httpd/unix-directory',
'size' => 18,
'etag' => 'abcdef',
'directory' => '/unexist',
'name' => 'new_name',
)));
$result = $this->files->rename($dir, $oldname, $newname);
$this->assertFalse($result['success']);
$this->assertEquals('targetnotfound', $result['data']['code']);
}
+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',
+67 -38
View File
@@ -938,16 +938,6 @@ describe('OCA.Files.FileList tests', function() {
describe('file previews', function() {
var previewLoadStub;
function getImageUrl($el) {
// might be slightly different cross-browser
var url = $el.css('background-image');
var r = url.match(/url\(['"]?([^'")]*)['"]?\)/);
if (!r) {
return url;
}
return r[1];
}
beforeEach(function() {
previewLoadStub = sinon.stub(OCA.Files.FileList.prototype, 'lazyLoadPreview');
});
@@ -961,7 +951,7 @@ describe('OCA.Files.FileList tests', function() {
};
var $tr = fileList.add(fileData);
var $td = $tr.find('td.filename');
expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
expect(OC.TestUtil.getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
expect(previewLoadStub.notCalled).toEqual(true);
});
it('renders default icon for dir when none provided and no preview is available', function() {
@@ -971,7 +961,7 @@ describe('OCA.Files.FileList tests', function() {
};
var $tr = fileList.add(fileData);
var $td = $tr.find('td.filename');
expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/folder.svg');
expect(OC.TestUtil.getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/folder.svg');
expect(previewLoadStub.notCalled).toEqual(true);
});
it('renders provided icon for file when provided', function() {
@@ -982,7 +972,7 @@ describe('OCA.Files.FileList tests', function() {
};
var $tr = fileList.add(fileData);
var $td = $tr.find('td.filename');
expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/application-pdf.svg');
expect(OC.TestUtil.getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/application-pdf.svg');
expect(previewLoadStub.notCalled).toEqual(true);
});
it('renders preview when no icon was provided and preview is available', function() {
@@ -993,11 +983,11 @@ describe('OCA.Files.FileList tests', function() {
};
var $tr = fileList.add(fileData);
var $td = $tr.find('td.filename');
expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
expect(OC.TestUtil.getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
expect(previewLoadStub.calledOnce).toEqual(true);
// third argument is callback
previewLoadStub.getCall(0).args[0].callback(OC.webroot + '/somepath.png');
expect(getImageUrl($td)).toEqual(OC.webroot + '/somepath.png');
expect(OC.TestUtil.getImageUrl($td)).toEqual(OC.webroot + '/somepath.png');
});
it('renders default file type icon when no icon was provided and no preview is available', function() {
var fileData = {
@@ -1007,7 +997,7 @@ describe('OCA.Files.FileList tests', function() {
};
var $tr = fileList.add(fileData);
var $td = $tr.find('td.filename');
expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
expect(OC.TestUtil.getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
expect(previewLoadStub.notCalled).toEqual(true);
});
});
@@ -1696,7 +1686,7 @@ describe('OCA.Files.FileList tests', function() {
var url = fakeServer.requests[0].url;
var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1));
expect(query.sort).toEqual('size');
expect(query.sortdirection).toEqual('asc');
expect(query.sortdirection).toEqual('desc');
});
it('Toggles sort direction when clicking on already sorted column', function() {
fileList.$el.find('.column-name .columntitle').click();
@@ -1710,37 +1700,51 @@ describe('OCA.Files.FileList tests', function() {
var ASC_CLASS = fileList.SORT_INDICATOR_ASC_CLASS;
var DESC_CLASS = fileList.SORT_INDICATOR_DESC_CLASS;
fileList.$el.find('.column-size .columntitle').click();
// moves triangle to size column
// moves triangle to size column, check indicator on name is hidden
expect(
fileList.$el.find('.column-name .sort-indicator').hasClass(ASC_CLASS + ' ' + DESC_CLASS)
).toEqual(false);
expect(
fileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS)
fileList.$el.find('.column-name .sort-indicator').hasClass('hidden')
).toEqual(true);
// click again on size column, reverses direction
fileList.$el.find('.column-size .columntitle').click();
// check indicator on size is visible and defaults to descending
expect(
fileList.$el.find('.column-size .sort-indicator').hasClass('hidden')
).toEqual(false);
expect(
fileList.$el.find('.column-size .sort-indicator').hasClass(DESC_CLASS)
).toEqual(true);
// click again on size column, reverses direction
fileList.$el.find('.column-size .columntitle').click();
expect(
fileList.$el.find('.column-size .sort-indicator').hasClass('hidden')
).toEqual(false);
expect(
fileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS)
).toEqual(true);
// click again on size column, reverses direction
fileList.$el.find('.column-size .columntitle').click();
expect(
fileList.$el.find('.column-size .sort-indicator').hasClass('hidden')
).toEqual(false);
expect(
fileList.$el.find('.column-size .sort-indicator').hasClass(DESC_CLASS)
).toEqual(true);
// click on mtime column, moves indicator there
fileList.$el.find('.column-mtime .columntitle').click();
expect(
fileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS + ' ' + DESC_CLASS)
fileList.$el.find('.column-size .sort-indicator').hasClass('hidden')
).toEqual(true);
expect(
fileList.$el.find('.column-mtime .sort-indicator').hasClass('hidden')
).toEqual(false);
expect(
fileList.$el.find('.column-mtime .sort-indicator').hasClass(ASC_CLASS)
fileList.$el.find('.column-mtime .sort-indicator').hasClass(DESC_CLASS)
).toEqual(true);
});
it('Uses correct sort comparator when inserting files', function() {
testFiles.sort(OCA.Files.FileList.Comparators.size);
testFiles.reverse(); //default is descending
// this will make it reload the testFiles with the correct sorting
fileList.$el.find('.column-size .columntitle').click();
expect(fakeServer.requests.length).toEqual(1);
@@ -1764,17 +1768,16 @@ describe('OCA.Files.FileList tests', function() {
etag: '999'
};
fileList.add(newFileData);
expect(fileList.findFileEl('Three.pdf').index()).toEqual(0);
expect(fileList.findFileEl('new file.txt').index()).toEqual(1);
expect(fileList.findFileEl('Two.jpg').index()).toEqual(2);
expect(fileList.findFileEl('somedir').index()).toEqual(3);
expect(fileList.findFileEl('One.txt').index()).toEqual(4);
expect(fileList.files.length).toEqual(5);
expect(fileList.$fileList.find('tr').length).toEqual(5);
expect(fileList.findFileEl('One.txt').index()).toEqual(0);
expect(fileList.findFileEl('somedir').index()).toEqual(1);
expect(fileList.findFileEl('Two.jpg').index()).toEqual(2);
expect(fileList.findFileEl('new file.txt').index()).toEqual(3);
expect(fileList.findFileEl('Three.pdf').index()).toEqual(4);
});
it('Uses correct reversed sort comparator when inserting files', function() {
testFiles.sort(OCA.Files.FileList.Comparators.size);
testFiles.reverse();
// this will make it reload the testFiles with the correct sorting
fileList.$el.find('.column-size .columntitle').click();
expect(fakeServer.requests.length).toEqual(1);
@@ -1811,13 +1814,13 @@ describe('OCA.Files.FileList tests', function() {
etag: '999'
};
fileList.add(newFileData);
expect(fileList.findFileEl('One.txt').index()).toEqual(0);
expect(fileList.findFileEl('somedir').index()).toEqual(1);
expect(fileList.findFileEl('Two.jpg').index()).toEqual(2);
expect(fileList.findFileEl('new file.txt').index()).toEqual(3);
expect(fileList.findFileEl('Three.pdf').index()).toEqual(4);
expect(fileList.files.length).toEqual(5);
expect(fileList.$fileList.find('tr').length).toEqual(5);
expect(fileList.findFileEl('One.txt').index()).toEqual(4);
expect(fileList.findFileEl('somedir').index()).toEqual(3);
expect(fileList.findFileEl('Two.jpg').index()).toEqual(2);
expect(fileList.findFileEl('new file.txt').index()).toEqual(1);
expect(fileList.findFileEl('Three.pdf').index()).toEqual(0);
});
});
/**
@@ -1933,4 +1936,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);
});
});
});
@@ -35,11 +35,12 @@ $encryptedRecoveryKey = $view->file_get_contents($keyPath);
$decryptedRecoveryKey = \OCA\Encryption\Crypt::decryptPrivateKey($encryptedRecoveryKey, $oldPassword);
if ($decryptedRecoveryKey) {
$encryptedRecoveryKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($decryptedRecoveryKey, $newPassword);
$view->file_put_contents($keyPath, $encryptedRecoveryKey);
$return = true;
$cipher = \OCA\Encryption\Helper::getCipher();
$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($decryptedRecoveryKey, $newPassword, $cipher);
if ($encryptedKey) {
\OCA\Encryption\Keymanager::setPrivateSystemKey($encryptedKey, $keyId . '.private.key');
$return = true;
}
}
\OC_FileProxy::$enabled = $proxyStatus;
@@ -35,13 +35,13 @@ $encryptedKey = $view->file_get_contents($keyPath);
$decryptedKey = \OCA\Encryption\Crypt::decryptPrivateKey($encryptedKey, $oldPassword);
if ($decryptedKey) {
$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($decryptedKey, $newPassword);
$view->file_put_contents($keyPath, $encryptedKey);
$session->setPrivateKey($decryptedKey);
$return = true;
$cipher = \OCA\Encryption\Helper::getCipher();
$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($decryptedKey, $newPassword, $cipher);
if ($encryptedKey) {
\OCA\Encryption\Keymanager::setPrivateKey($encryptedKey, $user);
$session->setPrivateKey($decryptedKey);
$return = true;
}
}
\OC_FileProxy::$enabled = $proxyStatus;
+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');
+111 -56
View File
@@ -136,6 +136,14 @@ class Hooks {
return $result;
}
/**
* remove keys from session during logout
*/
public static function logout() {
$session = new \OCA\Encryption\Session(new \OC\Files\View());
$session->removeKeys();
}
/**
* setup encryption backend upon user created
* @note This method should never be called for users using client side encryption
@@ -187,7 +195,6 @@ class Hooks {
* @param array $params keys: uid, password
*/
public static function setPassphrase($params) {
if (\OCP\App::isEnabled('files_encryption') === false) {
return true;
}
@@ -207,10 +214,14 @@ class Hooks {
$privateKey = $session->getPrivateKey();
// Encrypt private key with new user pwd as passphrase
$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($privateKey, $params['password']);
$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($privateKey, $params['password'], Helper::getCipher());
// Save private key
Keymanager::setPrivateKey($encryptedPrivateKey);
if ($encryptedPrivateKey) {
Keymanager::setPrivateKey($encryptedPrivateKey, \OCP\User::getUser());
} else {
\OCP\Util::writeLog('files_encryption', 'Could not update users encryption password', \OCP\Util::ERROR);
}
// NOTE: Session does not need to be updated as the
// private key has not changed, only the passphrase
@@ -245,16 +256,17 @@ class Hooks {
// Save public key
$view->file_put_contents('/public-keys/' . $user . '.public.key', $keypair['publicKey']);
// Encrypt private key empty passphrase
$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $newUserPassword);
// Encrypt private key with new password
$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($keypair['privateKey'], $newUserPassword, Helper::getCipher());
if ($encryptedKey) {
Keymanager::setPrivateKey($encryptedKey, $user);
// Save private key
$view->file_put_contents(
'/' . $user . '/files_encryption/' . $user . '.private.key', $encryptedPrivateKey);
if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files
$util = new Util($view, $user);
$util->recoverUsersFiles($recoveryPassword);
if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files
$util = new Util($view, $user);
$util->recoverUsersFiles($recoveryPassword);
}
} else {
\OCP\Util::writeLog('files_encryption', 'Could not update users encryption password', \OCP\Util::ERROR);
}
\OC_FileProxy::$enabled = $proxyStatus;
@@ -303,7 +315,7 @@ class Hooks {
}
/**
* @brief
* update share keys if a file was shared
*/
public static function postShared($params) {
@@ -313,34 +325,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 +407,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);
}
}
@@ -407,21 +431,51 @@ class Hooks {
$mp1 = $view->getMountPoint('/' . $user . '/files/' . $params['oldpath']);
$mp2 = $view->getMountPoint('/' . $user . '/files/' . $params['newpath']);
$type = $view->is_dir('/' . $user . '/files/' . $params['oldpath']) ? 'folder' : 'file';
if ($mp1 === $mp2) {
self::$renamedFiles[$params['oldpath']] = array(
'uid' => $ownerOld,
'path' => $pathOld);
'path' => $pathOld,
'type' => $type,
'operation' => 'rename',
);
}
}
/**
* after a file is renamed, rename its keyfile and share-keys also fix the file size and fix also the sharing
* @param array $params array with oldpath and newpath
*
* This function is connected to the rename signal of OC_Filesystem and adjust the name and location
* of the stored versions along the actual file
* mark file as renamed so that we know the original source after the file was renamed
* @param array $params with the old path and the new path
*/
public static function postRename($params) {
public static function preCopy($params) {
$user = \OCP\User::getUser();
$view = new \OC\Files\View('/');
$util = new Util($view, $user);
list($ownerOld, $pathOld) = $util->getUidAndFilename($params['oldpath']);
// we only need to rename the keys if the rename happens on the same mountpoint
// otherwise we perform a stream copy, so we get a new set of keys
$mp1 = $view->getMountPoint('/' . $user . '/files/' . $params['oldpath']);
$mp2 = $view->getMountPoint('/' . $user . '/files/' . $params['newpath']);
$type = $view->is_dir('/' . $user . '/files/' . $params['oldpath']) ? 'folder' : 'file';
if ($mp1 === $mp2) {
self::$renamedFiles[$params['oldpath']] = array(
'uid' => $ownerOld,
'path' => $pathOld,
'type' => $type,
'operation' => 'copy');
}
}
/**
* after a file is renamed/copied, rename/copy its keyfile and share-keys also fix the file size and fix also the sharing
*
* @param array $params array with oldpath and newpath
*/
public static function postRenameOrCopy($params) {
if (\OCP\App::isEnabled('files_encryption') === false) {
return true;
@@ -432,7 +486,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 +493,11 @@ class Hooks {
isset(self::$renamedFiles[$params['oldpath']]['path'])) {
$ownerOld = self::$renamedFiles[$params['oldpath']]['uid'];
$pathOld = self::$renamedFiles[$params['oldpath']]['path'];
$type = self::$renamedFiles[$params['oldpath']]['type'];
$operation = self::$renamedFiles[$params['oldpath']]['operation'];
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;
}
@@ -473,7 +529,7 @@ class Hooks {
}
// handle share keys
if (!$view->is_dir($oldKeyfilePath)) {
if ($type === 'file') {
$oldKeyfilePath .= '.key';
$newKeyfilePath .= '.key';
@@ -481,27 +537,22 @@ class Hooks {
$matches = Helper::findShareKeys($oldShareKeyPath, $view);
foreach ($matches as $src) {
$dst = \OC\Files\Filesystem::normalizePath(str_replace($pathOld, $pathNew, $src));
$view->rename($src, $dst);
$view->$operation($src, $dst);
}
} else {
// handle share-keys folders
$view->rename($oldShareKeyPath, $newShareKeyPath);
$view->$operation($oldShareKeyPath, $newShareKeyPath);
}
// Rename keyfile so it isn't orphaned
if ($view->file_exists($oldKeyfilePath)) {
$view->rename($oldKeyfilePath, $newKeyfilePath);
$view->$operation($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 +646,7 @@ class Hooks {
}
/**
* unmount file from yourself
* remember files/folders which get unmounted
*/
public static function preUmount($params) {
@@ -613,6 +665,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 +697,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);
}
}
}
+118 -36
View File
@@ -38,6 +38,11 @@ class Crypt {
const ENCRYPTION_PRIVATE_KEY_NOT_VALID_ERROR = 2;
const ENCRYPTION_NO_SHARE_KEY_FOUND = 3;
const BLOCKSIZE = 8192; // block size will always be 8192 for a PHP stream https://bugs.php.net/bug.php?id=21641
const DEFAULT_CIPHER = 'AES-256-CFB';
const HEADERSTART = 'HBEGIN';
const HEADEREND = 'HEND';
/**
* return encryption mode client or server side encryption
@@ -213,19 +218,22 @@ class Crypt {
* @param string $plainContent
* @param string $iv
* @param string $passphrase
* @param string $cypher used for encryption, currently we support AES-128-CFB and AES-256-CFB
* @return string encrypted file content
* @throws \OCA\Encryption\Exceptions\EncryptionException
*/
private static function encrypt($plainContent, $iv, $passphrase = '') {
private static function encrypt($plainContent, $iv, $passphrase = '', $cipher = Crypt::DEFAULT_CIPHER) {
if ($encryptedContent = openssl_encrypt($plainContent, 'AES-128-CFB', $passphrase, false, $iv)) {
return $encryptedContent;
} else {
\OCP\Util::writeLog('Encryption library', 'Encryption (symmetric) of content failed', \OCP\Util::ERROR);
\OCP\Util::writeLog('Encryption library', openssl_error_string(), \OCP\Util::ERROR);
return false;
$encryptedContent = openssl_encrypt($plainContent, $cipher, $passphrase, false, $iv);
if (!$encryptedContent) {
$error = "Encryption (symmetric) of content failed: " . openssl_error_string();
\OCP\Util::writeLog('Encryption library', $error, \OCP\Util::ERROR);
throw new Exceptions\EncryptionException($error, 50);
}
return $encryptedContent;
}
/**
@@ -233,19 +241,18 @@ class Crypt {
* @param string $encryptedContent
* @param string $iv
* @param string $passphrase
* @param string $cipher cipher user for decryption, currently we support aes128 and aes256
* @throws \Exception
* @return string decrypted file content
*/
private static function decrypt($encryptedContent, $iv, $passphrase) {
private static function decrypt($encryptedContent, $iv, $passphrase, $cipher = Crypt::DEFAULT_CIPHER) {
if ($plainContent = openssl_decrypt($encryptedContent, 'AES-128-CFB', $passphrase, false, $iv)) {
$plainContent = openssl_decrypt($encryptedContent, $cipher, $passphrase, false, $iv);
if ($plainContent) {
return $plainContent;
} else {
throw new \Exception('Encryption library: Decryption (symmetric) of content failed');
}
}
@@ -293,11 +300,12 @@ class Crypt {
* Symmetrically encrypts a string and returns keyfile content
* @param string $plainContent content to be encrypted in keyfile
* @param string $passphrase
* @param string $cypher used for encryption, currently we support AES-128-CFB and AES-256-CFB
* @return false|string encrypted content combined with IV
* @note IV need not be specified, as it will be stored in the returned keyfile
* and remain accessible therein.
*/
public static function symmetricEncryptFileContent($plainContent, $passphrase = '') {
public static function symmetricEncryptFileContent($plainContent, $passphrase = '', $cipher = Crypt::DEFAULT_CIPHER) {
if (!$plainContent) {
\OCP\Util::writeLog('Encryption library', 'symmetrically encryption failed, no content given.', \OCP\Util::ERROR);
@@ -306,15 +314,16 @@ class Crypt {
$iv = self::generateIv();
if ($encryptedContent = self::encrypt($plainContent, $iv, $passphrase)) {
try {
$encryptedContent = self::encrypt($plainContent, $iv, $passphrase, $cipher);
// Combine content to encrypt with IV identifier and actual IV
$catfile = self::concatIv($encryptedContent, $iv);
$padded = self::addPadding($catfile);
return $padded;
} else {
\OCP\Util::writeLog('Encryption library', 'Encryption (symmetric) of keyfile content failed', \OCP\Util::ERROR);
} catch (OCA\Encryption\Exceptions\EncryptionException $e) {
$message = 'Could not encrypt file content (code: ' . $e->getCode . '): ';
\OCP\Util::writeLog('files_encryption', $message . $e->getMessage, \OCP\Util::ERROR);
return false;
}
@@ -325,6 +334,7 @@ class Crypt {
* Symmetrically decrypts keyfile content
* @param string $keyfileContent
* @param string $passphrase
* @param string $cipher cipher used for decryption, currently aes128 and aes256 is supported.
* @throws \Exception
* @return string|false
* @internal param string $source
@@ -334,7 +344,7 @@ class Crypt {
*
* This function decrypts a file
*/
public static function symmetricDecryptFileContent($keyfileContent, $passphrase = '') {
public static function symmetricDecryptFileContent($keyfileContent, $passphrase = '', $cipher = Crypt::DEFAULT_CIPHER) {
if (!$keyfileContent) {
@@ -348,7 +358,7 @@ class Crypt {
// Split into enc data and catfile
$catfile = self::splitIv($noPadding);
if ($plainContent = self::decrypt($catfile['encrypted'], $catfile['iv'], $passphrase)) {
if ($plainContent = self::decrypt($catfile['encrypted'], $catfile['iv'], $passphrase, $cipher)) {
return $plainContent;
@@ -360,6 +370,7 @@ class Crypt {
/**
* Decrypt private key and check if the result is a valid keyfile
*
* @param string $encryptedKey encrypted keyfile
* @param string $passphrase to decrypt keyfile
* @return string|false encrypted private key or false
@@ -368,7 +379,15 @@ class Crypt {
*/
public static function decryptPrivateKey($encryptedKey, $passphrase) {
$plainKey = self::symmetricDecryptFileContent($encryptedKey, $passphrase);
$header = self::parseHeader($encryptedKey);
$cipher = self::getCipher($header);
// if we found a header we need to remove it from the key we want to decrypt
if (!empty($header)) {
$encryptedKey = substr($encryptedKey, strpos($encryptedKey, self::HEADEREND) + strlen(self::HEADEREND));
}
$plainKey = self::symmetricDecryptFileContent($encryptedKey, $passphrase, $cipher);
// check if this a valid private key
$res = openssl_pkey_get_private($plainKey);
@@ -390,6 +409,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 +417,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 +444,7 @@ class Crypt {
);
} else {
return false;
throw new Exceptions\MultiKeyEncryptException('multi key encryption failed: ' . openssl_error_string(), 20);
}
}
@@ -438,8 +454,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 +464,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 +472,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);
}
}
@@ -580,4 +590,76 @@ class Crypt {
}
}
/**
* read header into array
*
* @param string $data
* @return array
*/
public static function parseHeader($data) {
$result = array();
if (substr($data, 0, strlen(self::HEADERSTART)) === self::HEADERSTART) {
$endAt = strpos($data, self::HEADEREND);
$header = substr($data, 0, $endAt + strlen(self::HEADEREND));
// +1 to not start with an ':' which would result in empty element at the beginning
$exploded = explode(':', substr($header, strlen(self::HEADERSTART)+1));
$element = array_shift($exploded);
while ($element !== self::HEADEREND) {
$result[$element] = array_shift($exploded);
$element = array_shift($exploded);
}
}
return $result;
}
/**
* check if data block is the header
*
* @param string $data
* @return boolean
*/
public static function isHeader($data) {
if (substr($data, 0, strlen(self::HEADERSTART)) === self::HEADERSTART) {
return true;
}
return false;
}
/**
* get chiper from header
*
* @param array $header
* @throws \OCA\Encryption\Exceptions\EncryptionException
*/
public static function getCipher($header) {
$cipher = isset($header['cipher']) ? $header['cipher'] : 'AES-128-CFB';
if ($cipher !== 'AES-256-CFB' && $cipher !== 'AES-128-CFB') {
throw new \OCA\Encryption\Exceptions\EncryptionException('file header broken, no supported cipher defined', 40);
}
return $cipher;
}
/**
* generate header for encrypted file
*/
public static function generateHeader() {
$cipher = Helper::getCipher();
$header = self::HEADERSTART . ':cipher:' . $cipher . ':' . self::HEADEREND;
return $header;
}
}
+55
View File
@@ -0,0 +1,55 @@
<?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;
/**
* General encryption exception
* Possible Error Codes:
* 10 - unexpected end of encryption header
* 20 - unexpected blog size
* 30 - encryption header to large
* 40 - unknown cipher
* 50 - encryption failed
*/
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 {
}
+32 -12
View File
@@ -49,6 +49,7 @@ class Helper {
public static function registerUserHooks() {
\OCP\Util::connectHook('OC_User', 'post_login', 'OCA\Encryption\Hooks', 'login');
\OCP\Util::connectHook('OC_User', 'logout', 'OCA\Encryption\Hooks', 'logout');
\OCP\Util::connectHook('OC_User', 'post_setPassword', 'OCA\Encryption\Hooks', 'setPassphrase');
\OCP\Util::connectHook('OC_User', 'pre_setPassword', 'OCA\Encryption\Hooks', 'preSetPassphrase');
\OCP\Util::connectHook('OC_User', 'post_createUser', 'OCA\Encryption\Hooks', 'postCreateUser');
@@ -62,7 +63,9 @@ class Helper {
public static function registerFilesystemHooks() {
\OCP\Util::connectHook('OC_Filesystem', 'rename', 'OCA\Encryption\Hooks', 'preRename');
\OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Encryption\Hooks', 'postRename');
\OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Encryption\Hooks', 'postRenameOrCopy');
\OCP\Util::connectHook('OC_Filesystem', 'copy', 'OCA\Encryption\Hooks', 'preCopy');
\OCP\Util::connectHook('OC_Filesystem', 'post_copy', 'OCA\Encryption\Hooks', 'postRenameOrCopy');
\OCP\Util::connectHook('OC_Filesystem', 'post_delete', 'OCA\Encryption\Hooks', 'postDelete');
\OCP\Util::connectHook('OC_Filesystem', 'delete', 'OCA\Encryption\Hooks', 'preDelete');
\OCP\Util::connectHook('OC_Filesystem', 'post_umount', 'OCA\Encryption\Hooks', 'postUmount');
@@ -141,19 +144,17 @@ class Helper {
$view->file_put_contents('/public-keys/' . $recoveryKeyId . '.public.key', $keypair['publicKey']);
// Encrypt private key empty passphrase
$encryptedPrivateKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($keypair['privateKey'], $recoveryPassword);
// Save private key
$view->file_put_contents('/owncloud_private_key/' . $recoveryKeyId . '.private.key', $encryptedPrivateKey);
$cipher = \OCA\Encryption\Helper::getCipher();
$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($keypair['privateKey'], $recoveryPassword, $cipher);
if ($encryptedKey) {
Keymanager::setPrivateSystemKey($encryptedKey, $recoveryKeyId . '.private.key');
// Set recoveryAdmin as enabled
$appConfig->setValue('files_encryption', 'recoveryAdminEnabled', 1);
$return = true;
}
\OC_FileProxy::$enabled = true;
// Set recoveryAdmin as enabled
$appConfig->setValue('files_encryption', 'recoveryAdminEnabled', 1);
$return = true;
} else { // get recovery key and check the password
$util = new \OCA\Encryption\Util(new \OC\Files\View('/'), \OCP\User::getUser());
$return = $util->checkRecoveryPassword($recoveryPassword);
@@ -227,7 +228,6 @@ class Helper {
return $return;
}
/**
* checks if access is public/anonymous user
* @return bool
@@ -475,5 +475,25 @@ class Helper {
return false;
}
/**
* read the cipher used for encryption from the config.php
*
* @return string
*/
public static function getCipher() {
$cipher = \OCP\Config::getSystemValue('cipher', Crypt::DEFAULT_CIPHER);
if ($cipher !== 'AES-256-CFB' && $cipher !== 'AES-128-CFB') {
\OCP\Util::writeLog('files_encryption',
'wrong cipher defined in config.php, only AES-128-CFB and AES-256-CFB is supported. Fall back ' . Crypt::DEFAULT_CIPHER,
\OCP\Util::WARN);
$cipher = Crypt::DEFAULT_CIPHER;
}
return $cipher;
}
}
+41 -9
View File
@@ -258,9 +258,13 @@ class Keymanager {
* @note Encryption of the private key must be performed by client code
* as no encryption takes place here
*/
public static function setPrivateKey($key) {
public static function setPrivateKey($key, $user = '') {
$user = \OCP\User::getUser();
if ($user === '') {
$user = \OCP\User::getUser();
}
$header = Crypt::generateHeader();
$view = new \OC\Files\View('/' . $user . '/files_encryption');
@@ -271,7 +275,7 @@ class Keymanager {
$view->mkdir('');
}
$result = $view->file_put_contents($user . '.private.key', $key);
$result = $view->file_put_contents($user . '.private.key', $header . $key);
\OC_FileProxy::$enabled = $proxyStatus;
@@ -279,6 +283,33 @@ class Keymanager {
}
/**
* write private system key (recovery and public share key) to disk
*
* @param string $key encrypted key
* @param string $keyName name of the key file
* @return boolean
*/
public static function setPrivateSystemKey($key, $keyName) {
$header = Crypt::generateHeader();
$view = new \OC\Files\View('/owncloud_private_key');
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
if (!$view->file_exists('')) {
$view->mkdir('');
}
$result = $view->file_put_contents($keyName, $header . $key);
\OC_FileProxy::$enabled = $proxyStatus;
return $result;
}
/**
* store share key
*
@@ -444,17 +475,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);
+15 -5
View File
@@ -80,11 +80,13 @@ class Session {
$this->view->file_put_contents('/public-keys/' . $publicShareKeyId . '.public.key', $keypair['publicKey']);
// Encrypt private key empty passphrase
$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], '');
// Save private key
$this->view->file_put_contents(
'/owncloud_private_key/' . $publicShareKeyId . '.private.key', $encryptedPrivateKey);
$cipher = \OCA\Encryption\Helper::getCipher();
$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($keypair['privateKey'], '', $cipher);
if ($encryptedKey) {
Keymanager::setPrivateSystemKey($encryptedKey, $publicShareKeyId . '.private.key');
} else {
\OCP\Util::writeLog('files_encryption', 'Could not create public share keys', \OCP\Util::ERROR);
}
\OC_FileProxy::$enabled = $proxyStatus;
@@ -121,6 +123,14 @@ class Session {
}
/**
* remove keys from session
*/
public function removeKeys() {
\OC::$session->remove('publicSharePrivateKey');
\OC::$session->remove('privateKey');
}
/**
* Sets status of encryption app
* @param string $init INIT_SUCCESSFUL, INIT_EXECUTED, NOT_INITIALIZED
+77 -14
View File
@@ -2,9 +2,10 @@
/**
* ownCloud
*
* @author Robin Appelman
* @copyright 2012 Sam Tuke <samtuke@owncloud.com>, 2011 Robin Appelman
* <icewind1991@gmail.com>
* @author Bjoern Schiessle, Robin Appelman
* @copyright 2014 Bjoern Schiessle <schiessle@owncloud.com>
* 2012 Sam Tuke <samtuke@owncloud.com>,
* 2011 Robin Appelman <icewind1991@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
@@ -49,9 +50,11 @@ namespace OCA\Encryption;
* encryption proxies are used and keyfiles deleted.
*/
class Stream {
const PADDING_CHAR = '-';
private $plainKey;
private $encKeyfiles;
private $rawPath; // The raw path relative to the data dir
private $relPath; // rel path to users file dir
private $userId;
@@ -66,6 +69,9 @@ class Stream {
private $newFile; // helper var, we only need to write the keyfile for new files
private $isLocalTmpFile = false; // do we operate on a local tmp file
private $localTmpFile; // path of local tmp file
private $headerWritten = false;
private $containHeader = false; // the file contain a header
private $cipher; // cipher used for encryption/decryption
/**
* @var \OC\Files\View
@@ -87,6 +93,9 @@ class Stream {
*/
public function stream_open($path, $mode, $options, &$opened_path) {
// read default cipher from config
$this->cipher = Helper::getCipher();
// assume that the file already exist before we decide it finally in getKey()
$this->newFile = false;
@@ -150,6 +159,9 @@ class Stream {
}
$this->size = $this->rootView->filesize($this->rawPath);
$this->readHeader();
}
if ($this->isLocalTmpFile) {
@@ -178,6 +190,29 @@ class Stream {
}
private function readHeader() {
if ($this->isLocalTmpFile) {
$handle = fopen($this->localTmpFile, 'r');
} else {
$handle = $this->rootView->fopen($this->rawPath, 'r');
}
if (is_resource($handle)) {
$data = fread($handle, Crypt::BLOCKSIZE);
$header = Crypt::parseHeader($data);
$this->cipher = Crypt::getCipher($header);
// remeber that we found a header
if (!empty($header)) {
$this->containHeader = true;
}
fclose($handle);
}
}
/**
* Returns the current position of the file pointer
* @return int position of the file pointer
@@ -195,6 +230,11 @@ class Stream {
$this->flush();
// ignore the header and just overstep it
if ($this->containHeader) {
$offset += Crypt::BLOCKSIZE;
}
// this wrapper needs to return "true" for success.
// the fseek call itself returns 0 on succeess
return !fseek($this->handle, $offset, $whence);
@@ -204,25 +244,25 @@ class Stream {
/**
* @param int $count
* @return bool|string
* @throws \Exception
* @throws \OCA\Encryption\Exceptions\EncryptionException
*/
public function stream_read($count) {
$this->writeCache = '';
if ($count !== 8192) {
// $count will always be 8192 https://bugs.php.net/bug.php?id=21641
// This makes this function a lot simpler, but will break this class if the above 'bug' gets 'fixed'
if ($count !== Crypt::BLOCKSIZE) {
\OCP\Util::writeLog('Encryption library', 'PHP "bug" 21641 no longer holds, decryption system requires refactoring', \OCP\Util::FATAL);
die();
throw new \OCA\Encryption\Exceptions\EncryptionException('expected a blog size of 8192 byte', 20);
}
// Get the data from the file handle
$data = fread($this->handle, $count);
// if this block contained the header we move on to the next block
if (Crypt::isHeader($data)) {
$data = fread($this->handle, $count);
}
$result = null;
if (strlen($data)) {
@@ -236,7 +276,7 @@ class Stream {
} else {
// Decrypt data
$result = Crypt::symmetricDecryptFileContent($data, $this->plainKey);
$result = Crypt::symmetricDecryptFileContent($data, $this->plainKey, $this->cipher);
}
}
@@ -254,7 +294,7 @@ class Stream {
public function preWriteEncrypt($plainData, $key) {
// Encrypt data to 'catfile', which includes IV
if ($encrypted = Crypt::symmetricEncryptFileContent($plainData, $key)) {
if ($encrypted = Crypt::symmetricEncryptFileContent($plainData, $key, $this->cipher)) {
return $encrypted;
@@ -317,6 +357,25 @@ class Stream {
}
/**
* write header at beginning of encrypted file
*
* @throws Exceptions\EncryptionException
*/
private function writeHeader() {
$header = Crypt::generateHeader();
if (strlen($header) > Crypt::BLOCKSIZE) {
throw new Exceptions\EncryptionException('max header size exceeded', 30);
}
$paddedHeader = str_pad($header, Crypt::BLOCKSIZE, self::PADDING_CHAR, STR_PAD_RIGHT);
fwrite($this->handle, $paddedHeader);
$this->headerWritten = true;
}
/**
* Handle plain data from the stream, and write it in 8192 byte blocks
* @param string $data data to be written to disk
@@ -334,6 +393,10 @@ class Stream {
return strlen($data);
}
if ($this->headerWritten === false) {
$this->writeHeader();
}
// Disable the file proxies so that encryption is not
// automatically attempted when the file is written to disk -
// we are handling that separately here and we don't want to
+87 -24
View File
@@ -167,11 +167,12 @@ class Util {
\OC_FileProxy::$enabled = false;
// Encrypt private key with user pwd as passphrase
$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $passphrase);
$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $passphrase, Helper::getCipher());
// Save key-pair
if ($encryptedPrivateKey) {
$this->view->file_put_contents($this->privateKeyPath, $encryptedPrivateKey);
$header = crypt::generateHeader();
$this->view->file_put_contents($this->privateKeyPath, $header . $encryptedPrivateKey);
$this->view->file_put_contents($this->publicKeyPath, $keypair['publicKey']);
}
@@ -394,8 +395,14 @@ class Util {
&& $this->isEncryptedPath($path)
) {
// get the size from filesystem
$size = $this->view->filesize($path);
$offset = 0;
if ($this->containHeader($path)) {
$offset = Crypt::BLOCKSIZE;
}
// get the size from filesystem if the file contains a encryption header we
// we substract it
$size = $this->view->filesize($path) - $offset;
// fast path, else the calculation for $lastChunkNr is bogus
if ($size === 0) {
@@ -406,15 +413,15 @@ class Util {
// calculate last chunk nr
// next highest is end of chunks, one subtracted is last one
// we have to read the last chunk, we can't just calculate it (because of padding etc)
$lastChunkNr = ceil($size/ 8192) - 1;
$lastChunkSize = $size - ($lastChunkNr * 8192);
$lastChunkNr = ceil($size/ Crypt::BLOCKSIZE) - 1;
$lastChunkSize = $size - ($lastChunkNr * Crypt::BLOCKSIZE);
// open stream
$stream = fopen('crypt://' . $path, "r");
if (is_resource($stream)) {
// calculate last chunk position
$lastChunckPos = ($lastChunkNr * 8192);
$lastChunckPos = ($lastChunkNr * Crypt::BLOCKSIZE);
// seek to end
if (@fseek($stream, $lastChunckPos) === -1) {
@@ -448,6 +455,30 @@ class Util {
return $result;
}
/**
* check if encrypted file contain a encryption header
*
* @param string $path
* @return boolean
*/
private function containHeader($path) {
// Disable encryption proxy to read the raw data
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
$isHeader = false;
$handle = $this->view->fopen($path, 'r');
if (is_resource($handle)) {
$firstBlock = fread($handle, Crypt::BLOCKSIZE);
$isHeader = Crypt::isHeader($firstBlock);
}
\OC_FileProxy::$enabled = $proxyStatus;
return $isHeader;
}
/**
* fix the file size of the encrypted file
* @param string $path absolute path
@@ -956,19 +987,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',
@@ -1034,10 +1072,10 @@ class Util {
// check if it is a group mount
if (\OCP\App::isEnabled("files_external")) {
$mount = \OC_Mount_Config::getSystemMountPoints();
foreach ($mount as $mountPoint => $data) {
if ($mountPoint == substr($ownerPath, 1, strlen($mountPoint))) {
$userIds = array_merge($userIds, $this->getUserWithAccessToMountPoint($data['applicable']['users'], $data['applicable']['groups']));
$mounts = \OC_Mount_Config::getSystemMountPoints();
foreach ($mounts as $mount) {
if ($mount['mountpoint'] == substr($ownerPath, 1, strlen($mount['mountpoint']))) {
$userIds = array_merge($userIds, $this->getUserWithAccessToMountPoint($mount['applicable']['users'], $mount['applicable']['groups']));
}
}
}
@@ -1495,16 +1533,41 @@ class Util {
public function isSystemWideMountPoint($path) {
$normalizedPath = ltrim($path, '/');
if (\OCP\App::isEnabled("files_external")) {
$mount = \OC_Mount_Config::getSystemMountPoints();
foreach ($mount as $mountPoint => $data) {
if ($mountPoint == substr($normalizedPath, 0, strlen($mountPoint))) {
return true;
$mounts = \OC_Mount_Config::getSystemMountPoints();
foreach ($mounts as $mount) {
if ($mount['mountpoint'] == substr($normalizedPath, 0, strlen($mount['mountpoint']))) {
if ($this->isMountPointApplicableToUser($mount)) {
return true;
}
}
}
}
return false;
}
/**
* check if mount point is applicable to user
*
* @param array $mount contains $mount['applicable']['users'], $mount['applicable']['groups']
* @return boolean
*/
protected function isMountPointApplicableToUser($mount) {
$uid = \OCP\User::getUser();
$acceptedUids = array('all', $uid);
// check if mount point is applicable for the user
$intersection = array_intersect($acceptedUids, $mount['applicable']['users']);
if (!empty($intersection)) {
return true;
}
// check if mount point is applicable for group where the user is a member
foreach ($mount['applicable']['groups'] as $gid) {
if (\OC_Group::inGroup($uid, $gid)) {
return true;
}
}
return false;
}
/**
* decrypt private key and add it to the current session
* @param array $params with 'uid' and 'password'
+3
View File
@@ -12,8 +12,11 @@ $tmpl = new OCP\Template('files_encryption', 'settings-admin');
// Check if an adminRecovery account is enabled for recovering files after lost pwd
$recoveryAdminEnabled = \OC::$server->getAppConfig()->getValue('files_encryption', 'recoveryAdminEnabled', '0');
$session = new \OCA\Encryption\Session(new \OC\Files\View('/'));
$initStatus = $session->getInitialized();
$tmpl->assign('recoveryEnabled', $recoveryAdminEnabled);
$tmpl->assign('initStatus', $initStatus);
\OCP\Util::addscript('files_encryption', 'settings-admin');
\OCP\Util::addscript('core', 'multiselect');
@@ -1,6 +1,9 @@
<form id="encryption" class="section">
<h2><?php p($l->t('Encryption')); ?></h2>
<?php if($_["initStatus"] === \OCA\Encryption\Session::NOT_INITIALIZED): ?>
<?php p($l->t("Encryption App is enabled but your keys are not initialized, please log-out and log-in again")); ?>
<?php else: ?>
<p>
<?php p($l->t("Enable recovery key (allow to recover users files in case of password loss):")); ?>
<br/>
@@ -57,4 +60,5 @@
</button>
<span class="msg"></span>
</p>
<?php endif; ?>
</form>
@@ -1,18 +1,21 @@
<form id="encryption" class="section">
<h2><?php p( $l->t( 'Encryption' ) ); ?></h2>
<?php if ( $_["initialized"] === '1' ): ?>
<?php if ( $_["initialized"] === \OCA\Encryption\Session::NOT_INITIALIZED ): ?>
<?php p($l->t("Encryption App is enabled but your keys are not initialized, please log-out and log-in again")); ?>
<?php elseif ( $_["initialized"] === \OCA\Encryption\Session::INIT_EXECUTED ): ?>
<p>
<a name="changePKPasswd" />
<label for="changePrivateKeyPasswd">
<?php p( $l->t( "Your private key password no longer match your log-in password:" ) ); ?>
<em><?php p( $l->t( "Your private key password no longer match your log-in password." ) ); ?></em>
</label>
<br />
<em><?php p( $l->t( "Set your old private key password to your current log-in password." ) ); ?>
<?php p( $l->t( "Set your old private key password to your current log-in password:" ) ); ?>
<?php if ( $_["recoveryEnabledForUser"] ):
p( $l->t( " If you don't remember your old password you can ask your administrator to recover your files." ) );
endif; ?>
</em>
<br />
<input
type="password"
@@ -33,9 +36,8 @@
</button>
<span class="msg"></span>
</p>
<?php endif; ?>
<?php if ( $_["recoveryEnabled"] && $_["privateKeySet"] ): ?>
<?php elseif ( $_["recoveryEnabled"] && $_["privateKeySet"] && $_["initialized"] === \OCA\Encryption\Session::INIT_SUCCESSFUL ): ?>
<br />
<p>
<label for="userEnableRecovery"><?php p( $l->t( "Enable password recovery:" ) ); ?></label>
+134 -83
View File
@@ -95,6 +95,9 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
} else {
OC_App::disable('files_trashbin');
}
$this->assertTrue(\OC_FileProxy::$enabled);
\OCP\Config::deleteSystemValue('cipher');
}
public static function tearDownAfterClass() {
@@ -120,7 +123,9 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
// test successful decrypt
$crypted = Encryption\Crypt::symmetricEncryptFileContent($this->genPrivateKey, 'hat');
$decrypted = Encryption\Crypt::decryptPrivateKey($crypted, 'hat');
$header = Encryption\Crypt::generateHeader();
$decrypted = Encryption\Crypt::decryptPrivateKey($header . $crypted, 'hat');
$this->assertEquals($this->genPrivateKey, $decrypted);
@@ -150,6 +155,24 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
}
/**
* @medium
*/
function testSymmetricEncryptFileContentAes128() {
# TODO: search in keyfile for actual content as IV will ensure this test always passes
$crypted = Encryption\Crypt::symmetricEncryptFileContent($this->dataShort, 'hat', 'AES-128-CFB');
$this->assertNotEquals($this->dataShort, $crypted);
$decrypt = Encryption\Crypt::symmetricDecryptFileContent($crypted, 'hat', 'AES-128-CFB');
$this->assertEquals($this->dataShort, $decrypt);
}
/**
* @medium
*/
@@ -157,8 +180,6 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
$filename = 'tmp-' . uniqid() . '.test';
$util = new Encryption\Util(new \OC\Files\View(), $this->userId);
$cryptedFile = file_put_contents('crypt:///' . $this->userId . '/files/'. $filename, $this->dataShort);
// Test that data was successfully written
@@ -177,26 +198,52 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
// Check that the file was encrypted before being written to disk
$this->assertNotEquals($this->dataShort, $retreivedCryptedFile);
// Get the encrypted keyfile
$encKeyfile = Encryption\Keymanager::getFileKey($this->view, $util, $filename);
// Attempt to fetch the user's shareKey
$shareKey = Encryption\Keymanager::getShareKey($this->view, $this->userId, $util, $filename);
// get session
$session = new \OCA\Encryption\Session($this->view);
// get private key
$privateKey = $session->getPrivateKey($this->userId);
// Decrypt keyfile with shareKey
$plainKeyfile = Encryption\Crypt::multiKeyDecrypt($encKeyfile, $shareKey, $privateKey);
// Manually decrypt
$manualDecrypt = Encryption\Crypt::symmetricDecryptFileContent($retreivedCryptedFile, $plainKeyfile);
// Get file contents with the encryption wrapper
$decrypted = file_get_contents('crypt:///' . $this->userId . '/files/'. $filename);
// Check that decrypted data matches
$this->assertEquals($this->dataShort, $manualDecrypt);
$this->assertEquals($this->dataShort, $decrypted);
// Teardown
$this->view->unlink($this->userId . '/files/' . $filename);
Encryption\Keymanager::deleteFileKey($this->view, $filename);
}
/**
* @medium
*/
function testSymmetricStreamEncryptShortFileContentAes128() {
$filename = 'tmp-' . uniqid() . '.test';
\OCP\Config::setSystemValue('cipher', 'AES-128-CFB');
$cryptedFile = file_put_contents('crypt:///' . $this->userId . '/files/'. $filename, $this->dataShort);
// Test that data was successfully written
$this->assertTrue(is_int($cryptedFile));
\OCP\Config::deleteSystemValue('cipher');
// Disable encryption proxy to prevent recursive calls
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
// Get file contents without using any wrapper to get it's actual contents on disk
$retreivedCryptedFile = $this->view->file_get_contents($this->userId . '/files/' . $filename);
// Re-enable proxy - our work is done
\OC_FileProxy::$enabled = $proxyStatus;
// Check that the file was encrypted before being written to disk
$this->assertNotEquals($this->dataShort, $retreivedCryptedFile);
// Get file contents with the encryption wrapper
$decrypted = file_get_contents('crypt:///' . $this->userId . '/files/'. $filename);
// Check that decrypted data matches
$this->assertEquals($this->dataShort, $decrypted);
// Teardown
$this->view->unlink($this->userId . '/files/' . $filename);
@@ -216,8 +263,6 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
// Generate a a random filename
$filename = 'tmp-' . uniqid() . '.test';
$util = new Encryption\Util(new \OC\Files\View(), $this->userId);
// Save long data as encrypted file using stream wrapper
$cryptedFile = file_put_contents('crypt:///' . $this->userId . '/files/' . $filename, $this->dataLong . $this->dataLong);
@@ -238,50 +283,9 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
// Check that the file was encrypted before being written to disk
$this->assertNotEquals($this->dataLong . $this->dataLong, $retreivedCryptedFile);
// Manuallly split saved file into separate IVs and encrypted chunks
$r = preg_split('/(00iv00.{16,18})/', $retreivedCryptedFile, NULL, PREG_SPLIT_DELIM_CAPTURE);
$decrypted = file_get_contents('crypt:///' . $this->userId . '/files/'. $filename);
//print_r($r);
// Join IVs and their respective data chunks
$e = array();
$i = 0;
while ($i < count($r)-1) {
$e[] = $r[$i] . $r[$i+1];
$i = $i + 2;
}
//print_r($e);
// Get the encrypted keyfile
$encKeyfile = Encryption\Keymanager::getFileKey($this->view, $util, $filename);
// Attempt to fetch the user's shareKey
$shareKey = Encryption\Keymanager::getShareKey($this->view, $this->userId, $util, $filename);
// get session
$session = new \OCA\Encryption\Session($this->view);
// get private key
$privateKey = $session->getPrivateKey($this->userId);
// Decrypt keyfile with shareKey
$plainKeyfile = Encryption\Crypt::multiKeyDecrypt($encKeyfile, $shareKey, $privateKey);
// Set var for reassembling decrypted content
$decrypt = '';
// Manually decrypt chunk
foreach ($e as $chunk) {
$chunkDecrypt = Encryption\Crypt::symmetricDecryptFileContent($chunk, $plainKeyfile);
// Assemble decrypted chunks
$decrypt .= $chunkDecrypt;
}
$this->assertEquals($this->dataLong . $this->dataLong, $decrypt);
$this->assertEquals($this->dataLong . $this->dataLong, $decrypted);
// Teardown
@@ -293,14 +297,20 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
/**
* @medium
* Test that data that is read by the crypto stream wrapper
* Test that data that is written by the crypto stream wrapper with AES 128
* @note Encrypted data is manually prepared and decrypted here to avoid dependency on success of stream_read
* @note If this test fails with truncate content, check that enough array slices are being rejoined to form $e, as the crypt.php file may have gotten longer and broken the manual
* reassembly of its data
*/
function testSymmetricStreamDecryptShortFileContent() {
function testSymmetricStreamEncryptLongFileContentAes128() {
$filename = 'tmp-' . uniqid();
// Generate a a random filename
$filename = 'tmp-' . uniqid() . '.test';
\OCP\Config::setSystemValue('cipher', 'AES-128-CFB');
// Save long data as encrypted file using stream wrapper
$cryptedFile = file_put_contents('crypt:///'. $this->userId . '/files/' . $filename, $this->dataShort);
$cryptedFile = file_put_contents('crypt:///' . $this->userId . '/files/' . $filename, $this->dataLong . $this->dataLong);
// Test that data was successfully written
$this->assertTrue(is_int($cryptedFile));
@@ -309,39 +319,80 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
$this->assertTrue(Encryption\Crypt::isEncryptedMeta($filename));
\OCP\Config::deleteSystemValue('cipher');
// Get file contents without using any wrapper to get it's actual contents on disk
$retreivedCryptedFile = $this->view->file_get_contents($this->userId . '/files/' . $filename);
// Re-enable proxy - our work is done
\OC_FileProxy::$enabled = $proxyStatus;
// Get file decrypted contents
$decrypt = file_get_contents('crypt:///' . $this->userId . '/files/' . $filename);
$this->assertEquals($this->dataShort, $decrypt);
// Check that the file was encrypted before being written to disk
$this->assertNotEquals($this->dataLong . $this->dataLong, $retreivedCryptedFile);
$decrypted = file_get_contents('crypt:///' . $this->userId . '/files/'. $filename);
$this->assertEquals($this->dataLong . $this->dataLong, $decrypted);
// Teardown
// tear down
$this->view->unlink($this->userId . '/files/' . $filename);
Encryption\Keymanager::deleteFileKey($this->view, $filename);
}
/**
* @medium
* Test that data that is written by the crypto stream wrapper with AES 128
* @note Encrypted data is manually prepared and decrypted here to avoid dependency on success of stream_read
* @note If this test fails with truncate content, check that enough array slices are being rejoined to form $e, as the crypt.php file may have gotten longer and broken the manual
* reassembly of its data
*/
function testSymmetricStreamDecryptLongFileContent() {
function testStreamDecryptLongFileContentWithoutHeader() {
$filename = 'tmp-' . uniqid();
// Generate a a random filename
$filename = 'tmp-' . uniqid() . '.test';
\OCP\Config::setSystemValue('cipher', 'AES-128-CFB');
// Save long data as encrypted file using stream wrapper
$cryptedFile = file_put_contents('crypt:///' . $this->userId . '/files/' . $filename, $this->dataLong);
$cryptedFile = file_put_contents('crypt:///' . $this->userId . '/files/' . $filename, $this->dataLong . $this->dataLong);
\OCP\Config::deleteSystemValue('cipher');
// Test that data was successfully written
$this->assertTrue(is_int($cryptedFile));
// Get file decrypted contents
$decrypt = file_get_contents('crypt:///' . $this->userId . '/files/' . $filename);
// Disable encryption proxy to prevent recursive calls
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
$this->assertEquals($this->dataLong, $decrypt);
// Get file contents without using any wrapper to get it's actual contents on disk
$retreivedCryptedFile = $this->view->file_get_contents($this->userId . '/files/' . $filename);
// Check that the file was encrypted before being written to disk
$this->assertNotEquals($this->dataLong . $this->dataLong, $retreivedCryptedFile);
// remove the header to check if we can also decrypt old files without a header,
// this files should fall back to AES-128
$cryptedWithoutHeader = substr($retreivedCryptedFile, Encryption\Crypt::BLOCKSIZE);
$this->view->file_put_contents($this->userId . '/files/' . $filename, $cryptedWithoutHeader);
// Re-enable proxy - our work is done
\OC_FileProxy::$enabled = $proxyStatus;
$decrypted = file_get_contents('crypt:///' . $this->userId . '/files/'. $filename);
$this->assertEquals($this->dataLong . $this->dataLong, $decrypted);
// Teardown
// tear down
$this->view->unlink($this->userId . '/files/' . $filename);
Encryption\Keymanager::deleteFileKey($this->view, $filename);
}
/**
@@ -353,7 +404,7 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
$this->assertFalse(Encryption\Crypt::isCatfileContent($this->legacyEncryptedData));
$keyfileContent = Encryption\Crypt::symmetricEncryptFileContent($this->dataUrl, 'hat');
$keyfileContent = Encryption\Crypt::symmetricEncryptFileContent($this->dataUrl, 'hat', 'AES-128-CFB');
$this->assertTrue(Encryption\Crypt::isCatfileContent($keyfileContent));
+52
View File
@@ -335,6 +335,58 @@ class Test_Encryption_Hooks extends \PHPUnit_Framework_TestCase {
$this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder);
}
/**
* test rename operation
*/
function testCopyHook() {
// save file with content
$cryptedFile = file_put_contents('crypt:///' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->filename, $this->data);
// test that data was successfully written
$this->assertTrue(is_int($cryptedFile));
// check if keys exists
$this->assertTrue($this->rootView->file_exists(
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
. $this->filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
$this->assertTrue($this->rootView->file_exists(
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/'
. $this->filename . '.key'));
// make subfolder and sub-subfolder
$this->rootView->mkdir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder);
$this->rootView->mkdir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder);
$this->assertTrue($this->rootView->is_dir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder));
// copy the file to the sub-subfolder
\OC\Files\Filesystem::copy($this->filename, '/' . $this->folder . '/' . $this->folder . '/' . $this->filename);
$this->assertTrue($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->filename));
$this->assertTrue($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder . '/' . $this->filename));
// keys should be copied too
$this->assertTrue($this->rootView->file_exists(
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
. $this->filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
$this->assertTrue($this->rootView->file_exists(
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/'
. $this->filename . '.key'));
$this->assertTrue($this->rootView->file_exists(
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/' . $this->folder . '/' . $this->folder . '/'
. $this->filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
$this->assertTrue($this->rootView->file_exists(
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/' . $this->folder . '/' . $this->folder . '/'
. $this->filename . '.key'));
// cleanup
$this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder);
$this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->filename);
}
/**
* @brief replacing encryption keys during password change should be allowed
* until the user logged in for the first time
+36 -4
View File
@@ -107,7 +107,7 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
$key = Encryption\Keymanager::getPrivateKey($this->view, $this->userId);
$privateKey = Encryption\Crypt::symmetricDecryptFileContent($key, $this->pass);
$privateKey = Encryption\Crypt::decryptPrivateKey($key, $this->pass);
$res = openssl_pkey_get_private($privateKey);
@@ -174,6 +174,38 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
\OC_FileProxy::$enabled = $proxyStatus;
}
/**
* @medium
*/
function testSetPrivateKey() {
$key = "dummy key";
Encryption\Keymanager::setPrivateKey($key, 'dummyUser');
$this->assertTrue($this->view->file_exists('/dummyUser/files_encryption/dummyUser.private.key'));
//clean up
$this->view->deleteAll('/dummyUser');
}
/**
* @medium
*/
function testSetPrivateSystemKey() {
$key = "dummy key";
$keyName = "myDummyKey.private.key";
Encryption\Keymanager::setPrivateSystemKey($key, $keyName);
$this->assertTrue($this->view->file_exists('/owncloud_private_key/' . $keyName));
// clean up
$this->view->unlink('/owncloud_private_key/' . $keyName);
}
/**
* @medium
*/
@@ -189,7 +221,7 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
$this->assertArrayHasKey('key', $sslInfoPublic);
$privateKey = Encryption\Crypt::symmetricDecryptFileContent($keys['privateKey'], $this->pass);
$privateKey = Encryption\Crypt::decryptPrivateKey($keys['privateKey'], $this->pass);
$resPrivate = openssl_pkey_get_private($privateKey);
@@ -225,7 +257,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 +306,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');
+51 -3
View File
@@ -541,9 +541,9 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
. $this->filename . '.' . $publicShareKeyId . '.shareKey'));
// some hacking to simulate public link
$GLOBALS['app'] = 'files_sharing';
$GLOBALS['fileOwner'] = \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1;
\OC_User::setUserId(false);
//$GLOBALS['app'] = 'files_sharing';
//$GLOBALS['fileOwner'] = \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1;
\Test_Encryption_Util::logoutHelper();
// get file contents
$retrievedCryptedFile = file_get_contents('crypt:///' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename);
@@ -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');
}
}
+48 -1
View File
@@ -22,6 +22,9 @@ use OCA\Encryption;
class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
const TEST_ENCRYPTION_UTIL_USER1 = "test-util-user1";
const TEST_ENCRYPTION_UTIL_USER2 = "test-util-user2";
const TEST_ENCRYPTION_UTIL_GROUP1 = "test-util-group1";
const TEST_ENCRYPTION_UTIL_GROUP2 = "test-util-group2";
const TEST_ENCRYPTION_UTIL_LEGACY_USER = "test-legacy-user";
public $userId;
@@ -59,7 +62,15 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
// create test user
\Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1, true);
\Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER2, true);
\Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER, true);
// create groups
\OC_Group::createGroup(self::TEST_ENCRYPTION_UTIL_GROUP1);
\OC_Group::createGroup(self::TEST_ENCRYPTION_UTIL_GROUP2);
// add user 1 to group1
\OC_Group::addToGroup(self::TEST_ENCRYPTION_UTIL_USER1, self::TEST_ENCRYPTION_UTIL_GROUP1);
}
@@ -116,7 +127,11 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
public static function tearDownAfterClass() {
// cleanup test user
\OC_User::deleteUser(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1);
\OC_User::deleteUser(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER2);
\OC_User::deleteUser(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER);
//cleanup groups
\OC_Group::deleteGroup(self::TEST_ENCRYPTION_UTIL_GROUP1);
\OC_Group::deleteGroup(self::TEST_ENCRYPTION_UTIL_GROUP2);
}
/**
@@ -537,6 +552,29 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
$this->assertTrue($found);
}
/**
* @dataProvider dataProviderFortestIsMountPointApplicableToUser
*/
function testIsMountPointApplicableToUser($mount, $expectedResult) {
self::loginHelper(self::TEST_ENCRYPTION_UTIL_USER1);
$dummyClass = new DummyUtilClass($this->view, self::TEST_ENCRYPTION_UTIL_USER1);
$result = $dummyClass->testIsMountPointApplicableToUser($mount);
$this->assertSame($expectedResult, $result);
}
function dataProviderFortestIsMountPointApplicableToUser() {
return array(
array(array('applicable' => array('groups' => array(), 'users' => array(self::TEST_ENCRYPTION_UTIL_USER1))), true),
array(array('applicable' => array('groups' => array(), 'users' => array(self::TEST_ENCRYPTION_UTIL_USER2))), false),
array(array('applicable' => array('groups' => array(self::TEST_ENCRYPTION_UTIL_GROUP1), 'users' => array())), true),
array(array('applicable' => array('groups' => array(self::TEST_ENCRYPTION_UTIL_GROUP1), 'users' => array(self::TEST_ENCRYPTION_UTIL_USER2))), true),
array(array('applicable' => array('groups' => array(self::TEST_ENCRYPTION_UTIL_GROUP2), 'users' => array(self::TEST_ENCRYPTION_UTIL_USER2))), false),
array(array('applicable' => array('groups' => array(self::TEST_ENCRYPTION_UTIL_GROUP2), 'users' => array(self::TEST_ENCRYPTION_UTIL_USER2, 'all'))), true),
array(array('applicable' => array('groups' => array(self::TEST_ENCRYPTION_UTIL_GROUP2), 'users' => array('all'))), true),
);
}
/**
* @param string $user
* @param bool $create
@@ -570,7 +608,7 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
public static function logoutHelper() {
\OC_Util::tearDownFS();
\OC_User::setUserId('');
\OC_User::setUserId(false);
\OC\Files\Filesystem::tearDown();
}
@@ -587,3 +625,12 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
}
}
/**
* dummy class extends \OCA\Encryption\Util to access protected methods for testing
*/
class DummyUtilClass extends \OCA\Encryption\Util {
public function testIsMountPointApplicableToUser($mount) {
return $this->isMountPointApplicableToUser($mount);
}
}
+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) {
+20 -15
View File
@@ -56,6 +56,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
/**
* @param string $path
* @return string correctly encoded path
*/
private function normalizePath($path) {
$path = trim($path, '/');
@@ -72,6 +73,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 +125,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 +173,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 +267,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 +297,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 +314,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 +337,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 +385,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 +412,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 +420,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,8 +441,8 @@ class AmazonS3 extends \OC\Files\Storage\Common {
try {
$result = $this->connection->copyObject(array(
'Bucket' => $this->bucket,
'Key' => $path2,
'CopySource' => $this->bucket . '/' . $path1
'Key' => $this->cleanKey($path2),
'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1)
));
$this->testTimeout();
} catch (S3Exception $e) {
@@ -453,7 +458,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
$result = $this->connection->copyObject(array(
'Bucket' => $this->bucket,
'Key' => $path2 . '/',
'CopySource' => $this->bucket . '/' . $path1 . '/'
'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1 . '/')
));
$this->testTimeout();
} catch (S3Exception $e) {
@@ -535,7 +540,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']);
}
}
@@ -70,10 +70,6 @@ if(substr($path, 0, 1) === '/') {
$path = substr($path, 1);
}
if ($keepAspect === true) {
$maxY = $maxX;
}
if($maxX === 0 || $maxY === 0) {
\OC_Response::setStatus(\OC_Response::STATUS_BAD_REQUEST);
\OC_Log::write('core-preview', 'x and/or y set to 0', \OC_Log::DEBUG);
+35 -27
View File
@@ -24,30 +24,38 @@ OCP\Util::addScript('files_sharing', 'external');
OC_FileProxy::register(new OCA\Files\Share\Proxy());
\OCA\Files\App::getNavigationManager()->add(
array(
"id" => 'sharingin',
"appname" => 'files_sharing',
"script" => 'list.php',
"order" => 10,
"name" => $l->t('Shared with you')
)
);
\OCA\Files\App::getNavigationManager()->add(
array(
"id" => 'sharingout',
"appname" => 'files_sharing',
"script" => 'list.php',
"order" => 15,
"name" => $l->t('Shared with others')
)
);
\OCA\Files\App::getNavigationManager()->add(
array(
"id" => 'sharinglinks',
"appname" => 'files_sharing',
"script" => 'list.php',
"order" => 20,
"name" => $l->t('Shared by link')
)
);
$config = \OC::$server->getConfig();
if ($config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes') {
\OCA\Files\App::getNavigationManager()->add(
array(
"id" => 'sharingin',
"appname" => 'files_sharing',
"script" => 'list.php',
"order" => 10,
"name" => $l->t('Shared with you')
)
);
if (\OCP\Util::isSharingDisabledForUser() === false) {
\OCA\Files\App::getNavigationManager()->add(
array(
"id" => 'sharingout',
"appname" => 'files_sharing',
"script" => 'list.php',
"order" => 15,
"name" => $l->t('Shared with others')
)
);
\OCA\Files\App::getNavigationManager()->add(
array(
"id" => 'sharinglinks',
"appname" => 'files_sharing',
"script" => 'list.php',
"order" => 20,
"name" => $l->t('Shared by link')
)
);
}
}
+1
View File
@@ -15,4 +15,5 @@
<files>public.php</files>
<webdav>publicwebdav.php</webdav>
</public>
<ocsid>166050</ocsid>
</info>
+7 -2
View File
@@ -32,6 +32,7 @@ function updateFilePermissions($chunkSize = 99) {
}
}
$connection = \OC_DB::getConnection();
$chunkedPermissionList = array_chunk($updatedRows, $chunkSize, true);
foreach ($chunkedPermissionList as $subList) {
@@ -39,7 +40,7 @@ function updateFilePermissions($chunkSize = 99) {
//update share table
$ids = implode(',', array_keys($subList));
foreach ($subList as $id => $permission) {
$statement .= "WHEN " . $id . " THEN " . $permission . " ";
$statement .= "WHEN " . $connection->quote($id, \PDO::PARAM_INT) . " THEN " . $permission . " ";
}
$statement .= ' END WHERE `id` IN (' . $ids . ')';
@@ -95,6 +96,7 @@ function removeSharedFolder($mkdirs = true, $chunkSize = 99) {
}
$chunkedShareList = array_chunk($shares, $chunkSize, true);
$connection = \OC_DB::getConnection();
foreach ($chunkedShareList as $subList) {
@@ -102,7 +104,7 @@ function removeSharedFolder($mkdirs = true, $chunkSize = 99) {
//update share table
$ids = implode(',', array_keys($subList));
foreach ($subList as $id => $target) {
$statement .= "WHEN " . $id . " THEN '/Shared" . $target . "' ";
$statement .= "WHEN " . $connection->quote($id, \PDO::PARAM_INT) . " THEN " . $connection->quote('/Shared' . $target, \PDO::PARAM_STR);
}
$statement .= ' END WHERE `id` IN (' . $ids . ')';
@@ -111,5 +113,8 @@ function removeSharedFolder($mkdirs = true, $chunkSize = 99) {
$query->execute(array());
}
// set config to keep the Shared folder as the default location for new shares
\OCA\Files_Sharing\Helper::setShareFolder('/Shared');
}
}
+1 -1
View File
@@ -1 +1 @@
0.5.2
0.5.3
+39 -11
View File
@@ -2,7 +2,7 @@
background: #fff;
text-align: center;
margin: 45px auto 0;
min-height: 150px;
min-height: 600px;
}
#preview .notCreatable {
@@ -44,8 +44,9 @@ p.info a {
max-width:100%;
}
/* fix multiselect bar offset on shared page */
thead {
padding-left: 0 !important; /* fixes multiselect bar offset on shared page */
left: 0 !important;
}
#data-upload-form {
@@ -89,21 +90,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 () {
+2 -1
View File
@@ -73,6 +73,7 @@ OCA.Sharing.PublicApp = {
var params = {
x: $(document).width() * window.devicePixelRatio,
y: $(document).height() * window.devicePixelRatio,
a: 'true',
file: encodeURIComponent(this.initialDir + $('#filename').val()),
t: $('#sharingToken').val(),
@@ -163,7 +164,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;
+14 -16
View File
@@ -59,6 +59,9 @@
$tr.attr('data-share-id', _.pluck(fileData.shares, 'id').join(','));
if (this._sharedWithUser) {
$tr.attr('data-share-owner', fileData.shareOwner);
$tr.attr('data-mounttype', 'shared-root');
var permission = parseInt($tr.attr('data-permissions')) | OC.PERMISSION_DELETE;
$tr.attr('data-permissions', permission);
}
return $tr;
},
@@ -95,7 +98,6 @@
},
reload: function() {
var self = this;
this.showMask();
if (this._reloadCall) {
this._reloadCall.abort();
@@ -110,14 +112,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 +164,9 @@
}
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.isPreviewAvailable = true;
}
}
file.share = {
id: share.id,
@@ -185,7 +181,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 +235,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 +243,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);
}
});
+3 -3
View File
@@ -2,9 +2,9 @@
$TRANSLATIONS = array(
"Server to server sharing is not enabled on this server" => "La compartición sirvidor a sirvidor nun ta habilitada nesti sirvidor",
"Couldn't add remote share" => "Nun pudo amestase una compartición remota",
"Shared with you" => "Compartío contigo",
"Shared with others" => "Compartío con otros",
"Shared by link" => "Compartíu por enllaz",
"Shared with you" => "Compartíos contigo",
"Shared with others" => "Compartíos con otros",
"Shared by link" => "Compartíos per enllaz",
"No files have been shared with you yet." => "Entá nun se compartieron ficheros contigo.",
"You haven't shared any files yet." => "Entá nun compartiesti dengún ficheru.",
"You haven't shared any files by link yet." => "Entá nun compartiesti nengún ficheru por enllaz.",
+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);";
+33 -3
View File
@@ -60,8 +60,10 @@ 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;
}
return new \OC_OCS_Result($shares);
}
@@ -214,6 +216,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);
@@ -333,6 +338,8 @@ class Api {
return self::updatePassword($share, $params);
} elseif (isset($params['_put']['publicUpload'])) {
return self::updatePublicUpload($share, $params);
} elseif (isset($params['_put']['expireDate'])) {
return self::updateExpireDate($share, $params);
}
} catch (\Exception $e) {
@@ -403,7 +410,7 @@ class Api {
if ($share['item_type'] !== 'folder' ||
(int)$share['share_type'] !== \OCP\Share::SHARE_TYPE_LINK ) {
return new \OC_OCS_Result(null, 404, "public upload is only possible for public shared folders");
return new \OC_OCS_Result(null, 400, "public upload is only possible for public shared folders");
}
// read, create, update (7) if public upload is enabled or
@@ -414,6 +421,29 @@ class Api {
}
/**
* set expire date for public link share
* @param array $share information about the share
* @param array $params contains 'expireDate' which needs to be a well formated date string, e.g DD-MM-YYYY
* @return \OC_OCS_Result
*/
private static function updateExpireDate($share, $params) {
// only public links can have a expire date
if ((int)$share['share_type'] !== \OCP\Share::SHARE_TYPE_LINK ) {
return new \OC_OCS_Result(null, 400, "expire date only exists for public link shares");
}
try {
$expireDateSet = \OCP\Share::setExpirationDate($share['item_type'], $share['item_source'], $params['_put']['expireDate'], (int)$share['stime']);
$result = ($expireDateSet) ? new \OC_OCS_Result() : new \OC_OCS_Result(null, 404, "couldn't set expire date");
} catch (\Exception $e) {
$result = new \OC_OCS_Result(null, 404, $e->getMessage());
}
return $result;
}
/**
* update password for public link share
* @param array $share information about the share
@@ -549,7 +579,7 @@ class Api {
* @return array with: item_source, share_type, share_with, item_type, permissions
*/
private static function getShareFromId($shareID) {
$sql = 'SELECT `file_source`, `item_source`, `share_type`, `share_with`, `item_type`, `permissions` FROM `*PREFIX*share` WHERE `id` = ?';
$sql = 'SELECT `file_source`, `item_source`, `share_type`, `share_with`, `item_type`, `permissions`, `stime` FROM `*PREFIX*share` WHERE `id` = ?';
$args = array($shareID);
$query = \OCP\DB::prepare($sql);
$result = $query->execute($args);
+31 -78
View File
@@ -43,6 +43,7 @@ class Shared_Cache extends Cache {
/**
* Get the source cache of a shared file or folder
*
* @param string $target Shared target file path
* @return \OC\Files\Cache\Cache
*/
@@ -162,7 +163,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']);
}
@@ -275,12 +276,34 @@ class Shared_Cache extends Cache {
*/
public function search($pattern) {
$where = '`name` LIKE ? AND ';
$pattern = trim($pattern,'%');
// normalize pattern
$value = $this->normalize($pattern);
$normalizedPattern = $this->normalize($pattern);
return $this->searchWithWhere($where, $value);
$result = array();
$exploreDirs = array('');
while (count($exploreDirs) > 0) {
$dir = array_pop($exploreDirs);
$files = $this->getFolderContents($dir);
// no results?
if (!$files) {
// maybe it's a single shared file
$file = $this->get('');
if ($normalizedPattern === '' || stristr($file['name'], $normalizedPattern) !== false) {
$result[] = $file;
}
continue;
}
foreach ($files as $file) {
if ($normalizedPattern === '' || stristr($file['name'], $normalizedPattern) !== false) {
$result[] = $file;
}
if ($file['mimetype'] === 'httpd/unix-directory') {
$exploreDirs[] = ltrim($dir . '/' . $file['name'], '/');
}
}
}
return $result;
}
@@ -297,10 +320,6 @@ class Shared_Cache extends Cache {
$mimetype = null;
}
// note: searchWithWhere is currently broken as it doesn't
// recurse into subdirs nor returns the correct
// file paths, so using getFolderContents() for now
$result = array();
$exploreDirs = array('');
while (count($exploreDirs) > 0) {
@@ -326,57 +345,6 @@ class Shared_Cache extends Cache {
return $result;
}
/**
* The maximum number of placeholders that can be used in an SQL query.
* Value MUST be <= 1000 for oracle:
* see ORA-01795 maximum number of expressions in a list is 1000
* FIXME we should get this from doctrine as other DBs allow a lot more placeholders
*/
const MAX_SQL_CHUNK_SIZE = 1000;
/**
* search for files with a custom where clause and value
* the $wherevalue will be array_merge()d with the file id chunks
*
* @param string $sqlwhere
* @param string $wherevalue
* @return array
*/
private function searchWithWhere($sqlwhere, $wherevalue, $chunksize = self::MAX_SQL_CHUNK_SIZE) {
$ids = $this->getAll();
$files = array();
// divide into chunks
$chunks = array_chunk($ids, $chunksize);
foreach ($chunks as $chunk) {
$placeholders = join(',', array_fill(0, count($chunk), '?'));
$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
`encrypted`, `unencrypted_size`, `etag`
FROM `*PREFIX*filecache` WHERE ' . $sqlwhere . ' `fileid` IN (' . $placeholders . ')';
$stmt = \OC_DB::prepare($sql);
$result = $stmt->execute(array_merge(array($wherevalue), $chunk));
while ($row = $result->fetchRow()) {
if (substr($row['path'], 0, 6) === 'files/') {
$row['path'] = substr($row['path'], 6); // remove 'files/' from path as it's relative to '/Shared'
}
$row['mimetype'] = $this->getMimetype($row['mimetype']);
$row['mimepart'] = $this->getMimetype($row['mimepart']);
if ($row['encrypted'] or ($row['unencrypted_size'] > 0 and $row['mimetype'] === 'httpd/unix-directory')) {
$row['encrypted_size'] = $row['size'];
$row['size'] = $row['unencrypted_size'];
}
$files[] = $row;
}
}
return $files;
}
/**
* get the size of a folder and set it in the cache
*
@@ -436,8 +404,7 @@ class Shared_Cache extends Cache {
*/
public function getPathById($id, $pathEnd = '') {
// direct shares are easy
$path = $this->getShareById($id);
if (is_string($path)) {
if ($id === $this->storage->getSourceId()) {
return ltrim($pathEnd, '/');
} else {
// if the item is a direct share we try and get the path of the parent and append the name of the item to it
@@ -452,28 +419,14 @@ class Shared_Cache extends Cache {
/**
* @param integer $id
*/
private function getShareById($id) {
$item = \OCP\Share::getItemSharedWithBySource('file', $id);
if ($item) {
return trim($item['file_target'], '/');
}
$item = \OCP\Share::getItemSharedWithBySource('folder', $id);
if ($item) {
return trim($item['file_target'], '/');
}
return null;
}
/**
* @param integer $id
* @return array
*/
private function getParentInfo($id) {
$sql = 'SELECT `parent`, `name` FROM `*PREFIX*filecache` WHERE `fileid` = ?';
$query = \OC_DB::prepare($sql);
$result = $query->execute(array($id));
if ($row = $result->fetchRow()) {
return array($row['parent'], $row['name']);
return array((int)$row['parent'], $row['name']);
} else {
return array(-1, '');
}
+7 -2
View File
@@ -49,7 +49,12 @@ class Storage extends DAV implements ISharedStorage {
$this->remote = $options['remote'];
$this->remoteUser = $options['owner'];
list($protocol, $remote) = explode('://', $this->remote);
list($host, $root) = explode('/', $remote, 2);
if (strpos($remote, '/')) {
list($host, $root) = explode('/', $remote, 2);
} else {
$host = $remote;
$root = '';
}
$secure = $protocol === 'https';
$root = rtrim($root, '/') . '/public.php/webdav';
$this->mountPoint = $options['mountpoint'];
@@ -148,7 +153,7 @@ class Storage extends DAV implements ISharedStorage {
// ownCloud instance is gone, likely to be a temporary server configuration error
throw $e;
}
} catch(\Exception $shareException) {
} catch (\Exception $shareException) {
// todo, maybe handle 403 better and ask the user for a new password
throw $e;
}
+20
View File
@@ -237,4 +237,24 @@ class Helper {
return ($result === 'yes') ? true : false;
}
/**
* get default share folder
*
* @return string
*/
public static function getShareFolder() {
$shareFolder = \OCP\Config::getSystemValue('share_folder', '/');
return \OC\Files\Filesystem::normalizePath($shareFolder);
}
/**
* set default share folder
*
* @param string $shareFolder
*/
public static function setShareFolder($shareFolder) {
\OCP\Config::setSystemValue('share_folder', $shareFolder);
}
}
+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);
}
}
}
+14 -1
View File
@@ -61,7 +61,8 @@ class OC_Share_Backend_File implements OCP\Share_Backend_File_Dependent {
* @return string
*/
public function generateTarget($filePath, $shareWith, $exclude = null) {
$target = '/'.basename($filePath);
$shareFolder = \OCA\Files_Sharing\Helper::getShareFolder();
$target = \OC\Files\Filesystem::normalizePath($shareFolder . '/' . basename($filePath));
// for group shares we return the target right away
if ($shareWith === false) {
@@ -70,6 +71,18 @@ class OC_Share_Backend_File implements OCP\Share_Backend_File_Dependent {
\OC\Files\Filesystem::initMountPoints($shareWith);
$view = new \OC\Files\View('/' . $shareWith . '/files');
if (!$view->is_dir($shareFolder)) {
$dir = '';
$subdirs = explode('/', $shareFolder);
foreach ($subdirs as $subdir) {
$dir = $dir . '/' . $subdir;
if (!$view->is_dir($dir)) {
$view->mkdir($dir);
}
}
}
$excludeList = \OCP\Share::getItemsSharedWithUser('file', $shareWith, self::FORMAT_TARGET_NAMES);
if (is_array($exclude)) {
$excludeList = array_merge($excludeList, $exclude);
+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
@@ -48,10 +48,10 @@ class Shared extends \OC\Files\Storage\Common implements ISharedStorage {
/**
* get file cache of the shared item source
* @return string
* @return int
*/
public function getSourceId() {
return $this->share['file_source'];
return (int) $this->share['file_source'];
}
/**
+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());
}
+4
View File
@@ -100,6 +100,10 @@ if (isset($path)) {
$file = basename($path);
// Download the file
if (isset($_GET['download'])) {
if (!\OCP\App::isEnabled('files_encryption')) {
// encryption app requires the session to store the keys in
\OC::$server->getSession()->close();
}
if (isset($_GET['files'])) { // download selected files
$files = urldecode($_GET['files']);
$files_list = json_decode($files);
-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
+8 -3
View File
@@ -1,4 +1,9 @@
<?php /** @var $l OC_L10N */ ?>
<?php $thumbSize=1024; ?>
<?php if ( \OC\Preview::isMimeSupported($_['mimetype'])): /* This enables preview images for links (e.g. on Facebook, Google+, ...)*/?>
<link rel="image_src" href="<?php p(OCP\Util::linkToRoute( 'core_ajax_public_preview', array('x' => $thumbSize, 'y' => $thumbSize, 'file' => $_['directory_path'], 't' => $_['dirToken']))); ?>" />
<?php endif; ?>
<div id="notification-container">
<div id="notification" style="display: none;"></div>
</div>
@@ -19,10 +24,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">
@@ -42,7 +47,7 @@
</div>
<?php elseif (substr($_['mimetype'], 0, strpos($_['mimetype'], '/')) == 'video'): ?>
<div id="imgframe">
<video tabindex="0" controls="" autoplay="">
<video tabindex="0" controls="" preload="none">
<source src="<?php p($_['downloadURL']); ?>" type="<?php p($_['mimetype']); ?>" />
</video>
</div>
+73 -1
View File
@@ -938,6 +938,78 @@ class Test_Files_Sharing_Api extends Test_Files_Sharing_Base {
}
/**
* @medium
*/
function testUpdateShareExpireDate() {
$fileInfo = $this->view->getFileInfo($this->folder);
// enforce expire date, by default 7 days after the file was shared
\OCP\Config::setAppValue('core', 'shareapi_default_expire_date', 'yes');
\OCP\Config::setAppValue('core', 'shareapi_enforce_expire_date', 'yes');
$dateWithinRange = new \DateTime();
$dateWithinRange->add(new \DateInterval('P5D'));
$dateOutOfRange = new \DateTime();
$dateOutOfRange->add(new \DateInterval('P8D'));
$result = \OCP\Share::shareItem('folder', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK,
null, 1);
// share was successful?
$this->assertTrue(is_string($result));
$items = \OCP\Share::getItemShared('file', null);
// make sure that we found a link share
$this->assertEquals(1, count($items));
$linkShare = reset($items);
// update expire date to a valid value
$params = array();
$params['id'] = $linkShare['id'];
$params['_put'] = array();
$params['_put']['expireDate'] = $dateWithinRange->format('Y-m-d');
$result = Share\Api::updateShare($params);
$this->assertTrue($result->succeeded());
$items = \OCP\Share::getItemShared('file', $linkShare['file_source']);
$updatedLinkShare = reset($items);
// date should be changed
$this->assertTrue(is_array($updatedLinkShare));
$this->assertEquals($dateWithinRange->format('Y-m-d') . ' 00:00:00', $updatedLinkShare['expiration']);
// update expire date to a value out of range
$params = array();
$params['id'] = $linkShare['id'];
$params['_put'] = array();
$params['_put']['expireDate'] = $dateOutOfRange->format('Y-m-d');
$result = Share\Api::updateShare($params);
$this->assertFalse($result->succeeded());
$items = \OCP\Share::getItemShared('file', $linkShare['file_source']);
$updatedLinkShare = reset($items);
// date shouldn't be changed
$this->assertTrue(is_array($updatedLinkShare));
$this->assertEquals($dateWithinRange->format('Y-m-d') . ' 00:00:00', $updatedLinkShare['expiration']);
// cleanup
\OCP\Config::setAppValue('core', 'shareapi_default_expire_date', 'no');
\OCP\Config::setAppValue('core', 'shareapi_enforce_expire_date', 'no');
\OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, null);
}
/**
* @medium
* @depends testCreateShare
@@ -1158,7 +1230,7 @@ class Test_Files_Sharing_Api extends Test_Files_Sharing_Base {
$result = \OCP\Share::shareItem('file', $info->getId(), \OCP\Share::SHARE_TYPE_USER, \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2, 31);
$this->assertTrue($result);
$result = \OCP\Share::setExpirationDate('file', $info->getId() , $expireDate);
$result = \OCP\Share::setExpirationDate('file', $info->getId() , $expireDate, $now);
$this->assertTrue($result);
//manipulate stime so that both shares are older then the default expire date
+94 -3
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');
@@ -92,6 +95,80 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base {
parent::tearDown();
}
function searchDataProvider() {
return array(
array('%another%',
array(
array('name' => 'another too.txt', 'path' => 'subdir/another too.txt'),
array('name' => 'another.txt', 'path' => 'subdir/another.txt'),
)
),
array('%Another%',
array(
array('name' => 'another too.txt', 'path' => 'subdir/another too.txt'),
array('name' => 'another.txt', 'path' => 'subdir/another.txt'),
)
),
array('%dir%',
array(
array('name' => 'emptydir', 'path' => 'emptydir'),
array('name' => 'subdir', 'path' => 'subdir'),
array('name' => 'shareddir', 'path' => ''),
)
),
array('%Dir%',
array(
array('name' => 'emptydir', 'path' => 'emptydir'),
array('name' => 'subdir', 'path' => 'subdir'),
array('name' => 'shareddir', 'path' => ''),
)
),
array('%txt%',
array(
array('name' => 'bar.txt', 'path' => 'bar.txt'),
array('name' => 'another too.txt', 'path' => 'subdir/another too.txt'),
array('name' => 'another.txt', 'path' => 'subdir/another.txt'),
)
),
array('%Txt%',
array(
array('name' => 'bar.txt', 'path' => 'bar.txt'),
array('name' => 'another too.txt', 'path' => 'subdir/another too.txt'),
array('name' => 'another.txt', 'path' => 'subdir/another.txt'),
)
),
array('%',
array(
array('name' => 'bar.txt', 'path' => 'bar.txt'),
array('name' => 'emptydir', 'path' => 'emptydir'),
array('name' => 'subdir', 'path' => 'subdir'),
array('name' => 'another too.txt', 'path' => 'subdir/another too.txt'),
array('name' => 'another.txt', 'path' => 'subdir/another.txt'),
array('name' => 'not a text file.xml', 'path' => 'subdir/not a text file.xml'),
array('name' => 'shareddir', 'path' => ''),
)
),
array('%nonexistant%',
array(
)
),
);
}
/**
* we cannot use a dataProvider because that would cause the stray hook detection to remove the hooks
* that were added in setUpBeforeClass.
*/
function testSearch() {
foreach ($this->searchDataProvider() as $data) {
list($pattern, $expectedFiles) = $data;
$results = $this->sharedStorage->getCache()->search($pattern);
$this->verifyFiles($expectedFiles, $results);
}
}
/**
* Test searching by mime type
*/
@@ -112,8 +189,6 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base {
),
);
$this->verifyFiles($check, $results);
$this->verifyFiles($check, $results);
}
function testGetFolderContentsInRoot() {
@@ -133,11 +208,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 +232,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 +272,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
@@ -226,7 +317,7 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base {
}
}
}
$this->assertTrue(empty($results));
$this->assertEquals(array(), $results);
}
/**
+41
View File
@@ -0,0 +1,41 @@
<?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/>.
*
*/
require_once __DIR__ . '/base.php';
class Test_Files_Sharing_Helper extends Test_Files_Sharing_Base {
/**
* test set and get share folder
*/
function testSetGetShareFolder() {
$this->assertSame('/', \OCA\Files_Sharing\Helper::getShareFolder());
\OCA\Files_Sharing\Helper::setShareFolder('/Shared');
$this->assertSame('/Shared', \OCA\Files_Sharing\Helper::getShareFolder());
// cleanup
\OCP\Config::deleteSystemValue('share_folder');
}
}
+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));
}
}
+30
View File
@@ -125,6 +125,36 @@ class Test_Files_Sharing extends Test_Files_Sharing_Base {
}
function testShareWithDifferentShareFolder() {
$fileinfo = $this->view->getFileInfo($this->filename);
$folderinfo = $this->view->getFileInfo($this->folder);
$fileShare = \OCP\Share::shareItem('file', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER,
self::TEST_FILES_SHARING_API_USER2, 31);
$this->assertTrue($fileShare);
\OCA\Files_Sharing\Helper::setShareFolder('/Shared/subfolder');
$folderShare = \OCP\Share::shareItem('folder', $folderinfo['fileid'], \OCP\Share::SHARE_TYPE_USER,
self::TEST_FILES_SHARING_API_USER2, 31);
$this->assertTrue($folderShare);
self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
$this->assertTrue(\OC\Files\Filesystem::file_exists($this->filename));
$this->assertTrue(\OC\Files\Filesystem::file_exists('/Shared/subfolder/' . $this->folder));
//cleanup
self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
\OCP\Share::unshare('file', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER,
self::TEST_FILES_SHARING_API_USER2);
\OCP\Share::unshare('folder', $folderinfo['fileid'], \OCP\Share::SHARE_TYPE_USER,
self::TEST_FILES_SHARING_API_USER2);
\OCP\Config::deleteSystemValue('share_folder');
}
/**
* shared files should never have delete permissions
* @dataProvider DataProviderTestFileSharePermissions
+5 -3
View File
@@ -52,9 +52,11 @@ class Test_Files_Sharing_Storage extends Test_Files_Sharing_Base {
}
/**
* if the parent of the mount point is gone then the mount point should move up
*
* @medium
*/
function testDeleteParentOfMountPoint() {
function testParentOfMountPointIsGone() {
// share to user
$fileinfo = $this->view->getFileInfo($this->folder);
@@ -79,8 +81,8 @@ class Test_Files_Sharing_Storage extends Test_Files_Sharing_Base {
$this->assertFalse($user2View->is_dir($this->folder));
// delete the local folder
$result = $user2View->unlink('/localfolder');
$this->assertTrue($result);
$fullPath = \OC_Config::getValue('datadirectory') . '/' . self::TEST_FILES_SHARING_API_USER2 . '/files/localfolder';
rmdir($fullPath);
//enforce reload of the mount points
self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
+28 -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);
}
/**
@@ -143,8 +152,13 @@ class Test_Files_Sharing_Update_Routine extends Test_Files_Sharing_Base {
}
}
$shareFolder = \OCP\Config::getSystemValue('share_folder', '/');
$this->assertSame('/Shared', $shareFolder);
// cleanup
$this->cleanupSharedTable();
\OCP\Config::deleteSystemValue('share_folder');
}
@@ -167,6 +181,7 @@ class Test_Files_Sharing_Update_Routine extends Test_Files_Sharing_Base {
array(\OCP\Share::SHARE_TYPE_USER, 'folder', 'user2', 'admin', '/foo2'),
array(\OCP\Share::SHARE_TYPE_USER, 'file', 'user3', 'admin', '/foo3'),
array(\OCP\Share::SHARE_TYPE_USER, 'folder', 'user4', 'admin', '/foo4'),
array(\OCP\Share::SHARE_TYPE_USER, 'folder', 'user4', 'admin', "/foo'4"),
array(\OCP\Share::SHARE_TYPE_LINK, 'file', 'user1', 'admin', '/ShouldNotChange'),
array(\OCP\Share::SHARE_TYPE_CONTACT, 'contact', 'admin', 'user1', '/ShouldNotChange'),
@@ -228,6 +243,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 -6
View File
@@ -8,9 +8,4 @@ OC::$CLASSPATH['OCA\Files_Versions\Capabilities'] = 'files_versions/lib/capabili
OCP\Util::addscript('files_versions', 'versions');
OCP\Util::addStyle('files_versions', 'versions');
// Listen to write signals
OCP\Util::connectHook('OC_Filesystem', 'write', "OCA\Files_Versions\Hooks", "write_hook");
// Listen to delete and rename signals
OCP\Util::connectHook('OC_Filesystem', 'post_delete', "OCA\Files_Versions\Hooks", "remove_hook");
OCP\Util::connectHook('OC_Filesystem', 'delete', "OCA\Files_Versions\Hooks", "pre_remove_hook");
OCP\Util::connectHook('OC_Filesystem', 'rename', "OCA\Files_Versions\Hooks", "rename_hook");
\OCA\Files_Versions\Hooks::connectHooks();
+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
+29 -1
View File
@@ -14,6 +14,16 @@ namespace OCA\Files_Versions;
class Hooks {
public static function connectHooks() {
// Listen to write signals
\OCP\Util::connectHook('OC_Filesystem', 'write', "OCA\Files_Versions\Hooks", "write_hook");
// Listen to delete and rename signals
\OCP\Util::connectHook('OC_Filesystem', 'post_delete', "OCA\Files_Versions\Hooks", "remove_hook");
\OCP\Util::connectHook('OC_Filesystem', 'delete', "OCA\Files_Versions\Hooks", "pre_remove_hook");
\OCP\Util::connectHook('OC_Filesystem', 'rename', "OCA\Files_Versions\Hooks", "rename_hook");
\OCP\Util::connectHook('OC_Filesystem', 'copy', "OCA\Files_Versions\Hooks", "copy_hook");
}
/**
* listen to write event.
*/
@@ -69,7 +79,25 @@ class Hooks {
$oldpath = $params['oldpath'];
$newpath = $params['newpath'];
if($oldpath<>'' && $newpath<>'') {
Storage::rename( $oldpath, $newpath );
Storage::renameOrCopy($oldpath, $newpath, 'rename');
}
}
}
/**
* copy versions of copied files
* @param array $params array with oldpath and newpath
*
* This function is connected to the copy signal of OC_Filesystem and copies the
* the stored versions to the new location
*/
public static function copy_hook($params) {
if (\OCP\App::isEnabled('files_versions')) {
$oldpath = $params['oldpath'];
$newpath = $params['newpath'];
if($oldpath<>'' && $newpath<>'') {
Storage::renameOrCopy($oldpath, $newpath, 'copy');
}
}
}
+69 -35
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']));
}
}
}
@@ -158,9 +174,12 @@ class Storage {
}
/**
* rename versions of a file
* rename or copy versions of a file
* @param string $old_path
* @param string $new_path
* @param string $operation can be 'copy' or 'rename'
*/
public static function rename($old_path, $new_path) {
public static function renameOrCopy($old_path, $new_path, $operation) {
list($uid, $oldpath) = self::getUidAndFilename($old_path);
list($uidn, $newpath) = self::getUidAndFilename($new_path);
$versions_view = new \OC\Files\View('/'.$uid .'/files_versions');
@@ -172,18 +191,21 @@ class Storage {
return self::store($new_path);
}
self::expire($newpath);
if ( $files_view->is_dir($oldpath) && $versions_view->is_dir($oldpath) ) {
$versions_view->rename($oldpath, $newpath);
$versions_view->$operation($oldpath, $newpath);
} else if ( ($versions = Storage::getVersions($uid, $oldpath)) ) {
// create missing dirs if necessary
self::createMissingDirectories($newpath, new \OC\Files\View('/'. $uidn));
foreach ($versions as $v) {
$versions_view->rename($oldpath.'.v'.$v['version'], $newpath.'.v'.$v['version']);
$versions_view->$operation($oldpath.'.v'.$v['version'], $newpath.'.v'.$v['version']);
}
}
if (!$files_view->is_dir($newpath)) {
self::expire($newpath);
}
}
/**
@@ -220,7 +242,7 @@ class Storage {
return true;
}else if ( $versionCreated ) {
$users_view->unlink($version);
self::deleteVersion($users_view, $version);
}
}
return false;
@@ -238,34 +260,46 @@ class Storage {
public static function getVersions($uid, $filename, $userFullPath = '') {
$versions = array();
// fetch for old versions
$view = new \OC\Files\View('/' . $uid . '/' . self::VERSIONS_ROOT);
$view = new \OC\Files\View('/' . $uid . '/');
$pathinfo = pathinfo($filename);
$files = $view->getDirectoryContent($pathinfo['dirname']);
$versionedFile = $pathinfo['basename'];
foreach ($files as $file) {
if ($file['type'] === 'file') {
$pos = strrpos($file['path'], '.v');
$currentFile = substr($file['name'], 0, strrpos($file['name'], '.v'));
if ($currentFile === $versionedFile) {
$version = substr($file['path'], $pos + 2);
$key = $version . '#' . $filename;
$versions[$key]['cur'] = 0;
$versions[$key]['version'] = $version;
$versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($version);
if (empty($userFullPath)) {
$versions[$key]['preview'] = '';
} else {
$versions[$key]['preview'] = \OCP\Util::linkToRoute('core_ajax_versions_preview', array('file' => $userFullPath, 'version' => $version));
$dir = \OC\Files\Filesystem::normalizePath(self::VERSIONS_ROOT . '/' . $pathinfo['dirname']);
$dirContent = false;
if ($view->is_dir($dir)) {
$dirContent = $view->opendir($dir);
}
if ($dirContent === false) {
return $versions;
}
if (is_resource($dirContent)) {
while (($entryName = readdir($dirContent)) !== false) {
if (!\OC\Files\Filesystem::isIgnoredDir($entryName)) {
$pathparts = pathinfo($entryName);
$filename = $pathparts['filename'];
if ($filename === $versionedFile) {
$pathparts = pathinfo($entryName);
$timestamp = substr($pathparts['extension'], 1);
$filename = $pathparts['filename'];
$key = $timestamp . '#' . $filename;
$versions[$key]['version'] = $timestamp;
$versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($timestamp);
if (empty($userFullPath)) {
$versions[$key]['preview'] = '';
} else {
$versions[$key]['preview'] = \OCP\Util::linkToRoute('core_ajax_versions_preview', array('file' => $userFullPath, 'version' => $timestamp));
}
$versions[$key]['path'] = $pathinfo['dirname'] . '/' . $filename;
$versions[$key]['name'] = $versionedFile;
$versions[$key]['size'] = $view->filesize($dir . '/' . $entryName);
}
$versions[$key]['path'] = $filename;
$versions[$key]['name'] = $versionedFile;
$versions[$key]['size'] = $file['size'];
}
}
closedir($dirContent);
}
// sort with newest version first
@@ -472,7 +506,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 +520,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'];
+142
View File
@@ -20,6 +20,7 @@
*
*/
require_once __DIR__ . '/../appinfo/app.php';
require_once __DIR__ . '/../lib/versions.php';
/**
@@ -28,6 +29,32 @@ require_once __DIR__ . '/../lib/versions.php';
*/
class Test_Files_Versioning extends \PHPUnit_Framework_TestCase {
const TEST_VERSIONS_USER = 'test-versions-user';
const USERS_VERSIONS_ROOT = '/test-versions-user/files_versions';
private $rootView;
public static function setUpBeforeClass() {
// create test user
self::loginHelper(self::TEST_VERSIONS_USER, true);
}
public static function tearDownAfterClass() {
// cleanup test user
\OC_User::deleteUser(self::TEST_VERSIONS_USER);
}
function setUp() {
self::loginHelper(self::TEST_VERSIONS_USER);
$this->rootView = new \OC\Files\View();
if (!$this->rootView->file_exists(self::USERS_VERSIONS_ROOT)) {
$this->rootView->mkdir(self::USERS_VERSIONS_ROOT);
}
}
function tearDown() {
$this->rootView->deleteAll(self::USERS_VERSIONS_ROOT);
}
/**
* @medium
@@ -176,6 +203,121 @@ class Test_Files_Versioning extends \PHPUnit_Framework_TestCase {
);
}
function testRename() {
\OC\Files\Filesystem::file_put_contents("test.txt", "test file");
$t1 = time();
// second version is two weeks older, this way we make sure that no
// version will be expired
$t2 = $t1 - 60 * 60 * 24 * 14;
// create some versions
$v1 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t1;
$v2 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t2;
$v1Renamed = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t1;
$v2Renamed = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t2;
$this->rootView->file_put_contents($v1, 'version1');
$this->rootView->file_put_contents($v2, 'version2');
// execute rename hook of versions app
\OCA\Files_Versions\Storage::renameOrCopy("test.txt", "test2.txt", 'rename');
$this->assertFalse($this->rootView->file_exists($v1));
$this->assertFalse($this->rootView->file_exists($v2));
$this->assertTrue($this->rootView->file_exists($v1Renamed));
$this->assertTrue($this->rootView->file_exists($v2Renamed));
//cleanup
\OC\Files\Filesystem::unlink('test2.txt');
}
function testCopy() {
\OC\Files\Filesystem::file_put_contents("test.txt", "test file");
$t1 = time();
// second version is two weeks older, this way we make sure that no
// version will be expired
$t2 = $t1 - 60 * 60 * 24 * 14;
// create some versions
$v1 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t1;
$v2 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t2;
$v1Copied = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t1;
$v2Copied = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t2;
$this->rootView->file_put_contents($v1, 'version1');
$this->rootView->file_put_contents($v2, 'version2');
// execute copy hook of versions app
\OCA\Files_Versions\Storage::renameOrCopy("test.txt", "test2.txt", 'copy');
$this->assertTrue($this->rootView->file_exists($v1));
$this->assertTrue($this->rootView->file_exists($v2));
$this->assertTrue($this->rootView->file_exists($v1Copied));
$this->assertTrue($this->rootView->file_exists($v2Copied));
//cleanup
\OC\Files\Filesystem::unlink('test.txt');
\OC\Files\Filesystem::unlink('test2.txt');
}
/**
* test if we find all versions and if the versions array contain
* the correct 'path' and 'name'
*/
public function testGetVersions() {
$t1 = time();
// second version is two weeks older, this way we make sure that no
// version will be expired
$t2 = $t1 - 60 * 60 * 24 * 14;
// create some versions
$v1 = self::USERS_VERSIONS_ROOT . '/subfolder/test.txt.v' . $t1;
$v2 = self::USERS_VERSIONS_ROOT . '/subfolder/test.txt.v' . $t2;
$this->rootView->mkdir(self::USERS_VERSIONS_ROOT . '/subfolder/');
$this->rootView->file_put_contents($v1, 'version1');
$this->rootView->file_put_contents($v2, 'version2');
// execute copy hook of versions app
$versions = \OCA\Files_Versions\Storage::getVersions(self::TEST_VERSIONS_USER, '/subfolder/test.txt');
$this->assertSame(2, count($versions));
foreach ($versions as $version) {
$this->assertSame('/subfolder/test.txt', $version['path']);
$this->assertSame('test.txt', $version['name']);
}
//cleanup
$this->rootView->deleteAll(self::USERS_VERSIONS_ROOT . '/subfolder');
}
/**
* @param string $user
* @param bool $create
* @param bool $password
*/
public static function loginHelper($user, $create = false) {
if ($create) {
\OC_User::createUser($user, $user);
}
\OC_Util::tearDownFS();
\OC_User::setUserId('');
\OC\Files\Filesystem::tearDown();
\OC_User::setUserId($user);
\OC_Util::setupFS($user);
}
}
// extend the original class to make it possible to test protected methods
+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
+5
View File
@@ -127,3 +127,8 @@ select[multiple=multiple] + button {
.ldap_grey {
color: #777;
}
.outoftheway {
position: absolute;
left: -2000px;
}

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