Compare commits

...

690 Commits

Author SHA1 Message Date
Frank Karlitschek
2fb1ed84f2 7.0.3 RC3 2014-11-06 14:45:41 +01:00
Bjoern Schiessle
9255da9856 check if the provided password is really the current log-in password 2014-11-06 14:35:48 +01:00
Morris Jobke
4c46a2f162 Merge pull request #11964 from canepan/patch-1
Fixed a typo in translation
2014-11-06 09:49:30 +01:00
Bernhard Posselt
6d7a5bf943 fix typo in content type 2014-11-05 14:35:11 +01:00
Lukas Reschke
8d87a2c4a6 Support HTML in logo claim 2014-11-05 13:55:15 +01:00
Thomas Müller
42197e2ce3 Merge pull request #11944 from owncloud/stable7backportl10n
backport some strings to stable7
2014-11-05 13:50:19 +01:00
Thomas Müller
61c802935b adjust strings - fixed #11930 2014-11-05 13:44:30 +01:00
canepan
b6334a2da5 Fixed a typo in translation 2014-11-05 11:51:37 +01:00
Vincent Petry
acc547c5ff Merge pull request #11950 from owncloud/stable7-s2s-fixcertcheckwhennocertfile
[stable7] Check for cert bundle existence before using it
2014-11-04 20:52:41 +01:00
Vincent Petry
b509fdb30f Store curl error message directly 2014-11-04 20:06:17 +01:00
Vincent Petry
3f35822e24 Check for cert bundle existence before using it 2014-11-04 16:49:25 +01:00
Volkan Gezer
d5316623dc backport some strings to stable7 2014-11-04 14:36:44 +01:00
Vincent Petry
8339aeab9a Only rescan trash folder when checking deleted versions
This fix prevents the file scanner to rescan the WHOLE storage and reset
the etags by mistake.

Backport of 97a51c46ed from master
2014-11-04 14:17:32 +01:00
Morris Jobke
f718e88aa1 fix config.sample.php linebreak 2014-11-04 09:23:56 +01:00
Vincent Petry
38783b1ca3 Capitalize Checks in admin page 2014-11-03 15:46:40 +01:00
Jörn Friedrich Dreyer
56fade9a04 add driver options to config samples 2014-11-03 14:22:57 +01:00
Jörn Friedrich Dreyer
70e68280b8 allow passing driver options, fixes #11718 2014-11-03 14:22:27 +01:00
Bjoern Schiessle
840f5487aa don't move versions if only the mount point was renamed 2014-11-03 13:32:19 +01:00
Lukas Reschke
920e991c82 Fix typo 2014-11-03 13:31:58 +01:00
Bjoern Schiessle
67c3ab92cf get the source path and owner in a pre hook and the target path and owner in a
post hook
2014-11-03 13:31:45 +01:00
Lukas Reschke
3bc748d43f Merge pull request #11614 from owncloud/fix_files_external_s3_storage_id_migration
fix files_external storage id migration
2014-10-31 10:22:20 +01:00
Arthur Schiwon
aeb9e172a9 on xp'ed mode and switching configurations: save raw mode instead of toggling filter mode in tabs since their status is unknown and dealt with by the Wizard. Fixes #11848
Backport of 8a48b088ed from master
2014-10-31 08:39:36 +01:00
Lukas Reschke
1025c30885 Fix stupid copy paste fail
...
2014-10-30 16:09:52 +01:00
Morris Jobke
4aae60a375 update 3rdparty to match it's stable7 branch 2014-10-30 13:44:43 +01:00
Morris Jobke
05a15a9669 Merge pull request #11865 from owncloud/stable7-fix-enc-sharingtests
Properly register sharing hooks and proxies
2014-10-30 13:39:45 +01:00
Vincent Petry
43e01439d6 Properly register sharing hooks and proxies
This will fix failing tests when shares weren't cleant up on delete due
to missing hooks.

Added login for user1 in setUp().
2014-10-30 12:17:02 +01:00
Morris Jobke
2261db62f5 fix style of generated documentation 2014-10-29 20:39:18 +01:00
Jörn Friedrich Dreyer
496b62f2eb test files external amazon s3 storage id migration 2014-10-27 13:01:48 +01:00
Jörn Friedrich Dreyer
b5ab3d7fc0 fix files_external storage id migration 2014-10-27 13:01:36 +01:00
Frank Karlitschek
321a631d92 7.0.3RC1 2014-10-23 21:26:04 +02:00
Vincent Petry
a1ded6e9d7 Added encryption test when moving file as non-owner 2014-10-29 16:11:15 +01:00
Vincent Petry
68dd41a4c9 Fix warning with unset extension check 2014-10-29 16:11:11 +01:00
Vincent Petry
0d02aa7910 Fix moving share keys as non-owner to subdir
This fix gathers the share keys BEFORE a file is moved to make sure that
findShareKeys() is able to find all relevant keys when the file still
exists.

After the move/copy operation the keys are moved/copied to the target
dir.

Also: refactored preRename and preCopy into a single function to avoid
duplicate code.
2014-10-29 16:11:06 +01:00
Lukas Reschke
77a533a29d Close session for search
Make search non-blocking.
2014-10-29 15:29:35 +01:00
Lukas Reschke
84e942f121 Close session for avatar get
This somehow blocked the "Users" UI for me when having a lot of users. - Shouldn't hurt here.
2014-10-29 15:08:01 +01:00
Lukas Reschke
96c092ce14 Show login again instead of JSON if CSRF check fails
Previously a JSON error page was shown to the user in-case the CSRF token was not valid. This was confusing and prevented people from login.

With this at least the login page is shown again and not a JSON error message. I consider this as sufficient since adding a new error page just for this sake would uneededly make lib/base.php even more cluttered and this is a edge-case which optimally should anyways not happen that often.

This can be tested by opening the login page, then clearing the cookies, and trying to login.
2014-10-29 14:38:24 +01:00
Lukas Reschke
ec3f92e102 Merge pull request #11802 from owncloud/backport-10958
Backport #10958
2014-10-28 14:23:58 +01:00
Thomas Müller
15e357fa94 Merge pull request #11800 from owncloud/backport-MakeSupportedDBsConfigurable
Make supported DBs configurable within config.php
2014-10-28 10:05:32 +01:00
Vincent Petry
3ba1441c2d Properly catch 503 storage not available in getQuotaInfo
When doing a PROPFIND on the root and one of the mount points is not
available, the returned quota attributes will now be zero.

This fix prevents the expected exception to make the whole call fail.

Backport of 21d825ed6c from master
2014-10-28 09:53:24 +01:00
Robin Appelman
9c24dbbac7 Introduce cross-db ILIKE
adding ILIKE to AdapterSQLSrv

add test case for ILIKE with wildcard

Make sqlite LIKE case sensitive on default

Implement ILIKE for sqlite

Use ILIKE in cache search

Fix ILIKE without wildcards for oracle
2014-10-27 23:00:49 +01:00
Lukas Reschke
9b908c3d05 Make supported DBs configurable within config.php
This commit will make the supported DBs for installation configurable within config.php. By default the following databases are tested: "sqlite", "mysql", "pgsql". The reason behind this is that there might be instances where we want to prevent SQLite to be used by mistake.

To test this play around with the new configuration parameter "supportedDatabases".

Conflicts:
	lib/private/util.php
2014-10-27 22:27:57 +01:00
Lukas Reschke
dbfe5ef28a Make files non executable
There is not much sense in having these files marked executable, we should avoid that.
2014-10-24 14:16:37 +02:00
Vincent Petry
a3c33585e1 Merge pull request #11719 from owncloud/stable7-fix-s3-regression
[stable7] Fix S3 connection regression
2014-10-23 12:36:35 +02:00
Vincent Petry
6a8c41754f Fix S3 connection 2014-10-22 21:48:22 +02:00
Arthur Schiwon
3399930355 Backport of #11702
set up FS by username, not login name\!

better variable name
2014-10-22 17:58:09 +02:00
Vincent Petry
09d80ac9fc Lazy initialize external storages
Backport of 075e8d8e86 from master
2014-10-22 14:50:22 +02:00
Jörn Friedrich Dreyer
6995b96589 guess mimetype on touch
Conflicts:
	apps/files_external/lib/amazons3.php
2014-10-22 12:35:59 +02:00
Vincent Petry
5624968d1e Merge pull request #11691 from owncloud/fix_pub_link_creation
fix target creation for public links
2014-10-22 11:06:12 +02:00
Bjoern Schiessle
a5d6e6eb28 if it is not a folder share the path already points to the correct file 2014-10-21 16:20:28 +02:00
Bjoern Schiessle
098b4e9e81 always use the correct share type 2014-10-21 16:19:54 +02:00
Lukas Reschke
d0b3fe2414 Merge pull request #11673 from owncloud/backport-11593
Backport #11593
2014-10-20 20:09:27 +02:00
Vincent Petry
3e02e79154 Add proper setup and teardown
Properly restore REQUEST_URI and SCRIPT_NAME after test runs
2014-10-20 19:15:50 +02:00
Lukas Reschke
841998bdcb Add "$_SERVER['REQUEST_URI']" to fix the unit tests
Let's hope that works
2014-10-20 19:15:45 +02:00
Lukas Reschke
ff93c5859b Refer to relative path instead of absolute path
There is no need to refer to the absolute path here if we can use the relative one.

Conflicts:
	lib/private/templatelayout.php

Conflicts:
	lib/private/templatelayout.php
2014-10-20 19:15:23 +02:00
Lukas Reschke
7ffe80cf25 Add unit tests for convertToRelativePath 2014-10-20 19:09:47 +02:00
Arthur Schiwon
86daab2800 backport of #11494
fix retrievel of group members and cache group members

fix changed variable name

with several backends, more than limit can be returned

make performance less bad. Still far from good, but at least it works

add one simple cache test

adjust group manager tests
2014-10-18 00:17:43 +02:00
macjohnny
174ab07a4b backport of #9104
Update manager.php

add caching to getUserGroupIds

Update manager.php

added description and blank lines in getUserGroupIds

Update manager.php

defined $uid in getUserGroupIds

Update manager.php

Update manager.php

Update manager.php

clean up function getUserGroupIds

clean up of function getUserGroupIds and improved caching mechanism of cachedUserGroupIds

modified caching mechanism in getUserGroupIds

removed cachedUserGroupIds, instead changed indexing in getUserGroups to groupId

adapted tests for a groupId indexed group array
2014-10-17 21:14:01 +02:00
Vincent Petry
9998861402 Encapsulate require_once to avoid name space bleedind
The script required by require_once might use variable names like $app
which will conflict with the code that follows.

This fix encapsulates require_once into its own function to avoid such
issues.
2014-10-17 15:03:53 +02:00
Thomas Müller
5b6ba39734 fixing usage of EncryptionException 2014-10-17 14:18:46 +02:00
Thomas Müller
fb9c2b5362 Merge pull request #11610 from owncloud/fix-svg-s7
Fix SVG icons
2014-10-17 12:02:03 +02:00
Bjoern Schiessle
a93e424738 set password field placeholder back if passward was disabled 2014-10-17 11:34:26 +02:00
Morris Jobke
d27db8cc61 fix the RST syntax of config.sample.php 2014-10-17 10:13:37 +02:00
Jörn Friedrich Dreyer
0c6a58a883 Merge pull request #11550 from owncloud/fix_flickering_users
fix flickering users in files external
2014-10-17 09:50:06 +02:00
Morris Jobke
d2b766ddee read config.sample.php options and whitespace fixes 2014-10-17 00:37:02 +02:00
Carla Schroder
da10518278 commented out instanceid and passwordsalt 2014-10-17 00:36:53 +02:00
Carla Schroder
9ed35a377f some small tweaks 2014-10-17 00:36:49 +02:00
Carla Schroder
c8c182b36e small corrections to config.sample.php 2014-10-17 00:36:38 +02:00
Lukas Reschke
f2dadc7104 Add a try catch block
This function might also be called before ownCloud is setup which results in a PHP fatal error. We therefore should gracefully catch errors in there.
2014-10-16 22:28:12 +02:00
Arthur Schiwon
ada93bab0c backport of #11478
add checkbox for experienced users to server tab

must be empty not auto

sets user filters to raw mode when marking user as experienced

Objectlasses, Groups and Attributes are now loaded only in assisted mode and only once

user and group counts are only upated on demand in experienced mode

confirmation before switching to assisted mode when admin is experienced

rename internal var name to avoid collision

more beautiful white spaces

smaller corrections to make scruitinizer happier, no effective changes

bump version

fix triggering of group update counts. improves the basic code which is also responsible for user counts. i did not find regressions, please doublecheck

remove debug output

coding style, no effective code changes

always abort running ajax request when the method is fired up again

show a spinner next to test filter button when the test is running

show Spinner when stuff is being saved

show busy cursor and lock tabs on save

instead of dis/enabling tabs on save, cancel tab change. avoids noisy ui

remove debug output

rephrase xp'ed user mode label

left-align checkbox on server tab
2014-10-16 16:24:04 +02:00
Jörn Friedrich Dreyer
b3acb2a1e7 Merge pull request #10732 from owncloud/make_skeleton_compatible_with_objectstore_minimal_stable7
make skeleton compatible with objectstore
2014-10-16 16:05:56 +02:00
Thomas Müller
af72528570 include the apps' versions hash to invalidate the cached assets 2014-10-16 15:55:33 +02:00
Lukas Reschke
5f69b2095a Add app version to JS and CSS
This leads to the regeneration of the hash in case a single application is updated.

Fixes https://github.com/owncloud/core/issues/11374
2014-10-16 15:55:27 +02:00
Bjoern Schiessle
6e3a7ea140 strip whitespace from the beginning and end of the display name to avoid empty display names 2014-10-16 14:55:07 +02:00
Lukas Reschke
38c817c7dd Merge pull request #11613 from owncloud/external-share-self-signed-stable7
[stable7] Use certificate bundle from files_external for external shares
2014-10-16 14:33:32 +02:00
Jörn Friedrich Dreyer
61873e4e21 throw exception in writeBack, the returned boolean is checked nowhere 2014-10-16 14:10:28 +02:00
Jörn Friedrich Dreyer
6d4d5400d7 make tests compatible with hook based skeleton generation 2014-10-16 14:10:28 +02:00
Jörn Friedrich Dreyer
92d069ab6c make skeleton compatible with objectstore
suspend encryption proxy when copying skeleton
2014-10-16 14:10:15 +02:00
Robin Appelman
d0101668ce Use certificate bundle from files_external for external shares 2014-10-16 13:42:42 +02:00
Lukas Reschke
73a5e3810f Remove insane comment 2014-10-16 12:40:09 +02:00
Lukas Reschke
6d81789a66 Fix SVG icons
FIXME: Ugly hack to prevent SVG of being returned if the SVG
provider is not enabled.
This is required because the preview system is designed in a
bad way and relies on opt-in with asterisks (i.e. image/*)
which will lead to the fact that a SVG will also match the image
provider.

Conflicts:
	lib/private/preview.php
2014-10-16 12:31:40 +02:00
Frank Karlitschek
e1e501b17f 7.0.3 RC1 2014-10-16 08:26:55 +02:00
Lukas Reschke
f53ecea372 Add darwin to if block
Otherwise it would fall into the 'win' else block because strpos($os, 'win') does also match 'darwin' what is the `php_uname` for OS X.
2014-10-15 22:26:03 +02:00
Lukas Reschke
15b39a6566 Merge pull request #11520 from owncloud/make-trash-objectstore-compatible
make trashbin compatible with objectstore
2014-10-15 22:24:59 +02:00
Lukas Reschke
508ef23486 Use rawurlencode since this seems to be expected by cURL
Fixes https://github.com/owncloud/core/pull/11501#issuecomment-58794405

Conflicts:
	tests/lib/largefilehelpergetfilesize.php
2014-10-15 20:00:51 +02:00
Robin Appelman
ff8d755702 Merge pull request #11567 from owncloud/cache-updater-refactor-stable7
[stable7] Refactor cache updater to work outside of the users home
2014-10-15 17:00:01 +02:00
Clark Tomlinson
9159598d32 Adding test helper to test private methods 2014-10-15 14:43:09 +02:00
Vincent Petry
361d6892c9 Allow specifying protocol in ext storage OC config
Allow specifying a protocol in the host field when mounting another
ownCloud instance. Note that this was already possible with the WebDAV
config but this bug made it inconsistent.
2014-10-15 14:17:49 +02:00
Vincent Petry
149b55f8ae Use body element when animating scroll in public page
In the public page the scroll container is the window instead of a div.
The $(window) object doesn't support animating the scroll property, so
the $('body') element is used instead.

Backport of 704ffaa6a3 from master
2014-10-15 10:53:25 +02:00
Thomas Müller
e7a46ed9e7 Merge pull request #11408 from owncloud/refactor-mailsettings-controller
Refactor MailSettings controller
2014-10-14 15:34:13 +02:00
Robin Appelman
ad06cd4b1f Remove explicit propagate calls 2014-10-14 14:38:31 +02:00
Robin Appelman
f03142f3d6 remove unstable test 2014-10-14 14:38:25 +02:00
Robin Appelman
0497136d29 Update cache before post hooks 2014-10-14 14:38:11 +02:00
Robin Appelman
c7f26fa969 Fix warning in homecache 2014-10-14 14:37:37 +02:00
Robin Appelman
ad6e281586 Fix unit test 2014-10-14 14:37:31 +02:00
Robin Appelman
595d43ab35 Improve unit tests for Cache\Updater 2014-10-14 14:37:22 +02:00
Robin Appelman
45738b6b7a Refactor Cache\Updater to work outside of the users home 2014-10-14 14:37:10 +02:00
Joas Schilling
ba33fd6a07 Set overwritemailurl* configs on setup
Correctly use overwritemailurl value when generating absolute urls in CLI

Fix #11500

Rename the config to *cli

Add overwrite.cli.url to the sample config

Revert separator fix, fixes unit test

Backport of 0407bc0978 from master
2014-10-14 09:20:23 +02:00
Thomas Müller
19585f9c3b fixing typos 2014-10-14 04:49:29 +02:00
Lukas Reschke
da150afcc5 Close session when loading apps
Otherwise the session is blocked while all remote apps are loaded. This can be very annoying especially when apps.owncloud.com is down or not reachable.
2014-10-13 18:00:38 +02:00
Jörn Friedrich Dreyer
496b68e2ad fix flickering users 2014-10-13 17:51:38 +02:00
Vincent Petry
d6a380613f Retrieve storage numeric id earlier when still available
The numeric id is only available before the storage entry is deleted, so
get it at that time.

Backport of d485c0098d from master
2014-10-13 17:37:33 +02:00
Vincent Petry
c38454a5b2 Revert "Set overwrite.cli.url configs on setup"
This reverts commit 48a1e69f5e.

This was backported too quickly.
2014-10-13 17:30:53 +02:00
Bjoern Schiessle
e4fa642e54 improved unit tests 2014-10-13 17:27:02 +02:00
Bjoern Schiessle
697f461ded always take unencrypted size 2014-10-13 17:26:38 +02:00
Joas Schilling
48a1e69f5e Set overwrite.cli.url configs on setup
Correctly use overwrite.cli.url value when generating absolute urls in CLI

Fix #11500

Backport and squash of the following from master, in order:
- f0fcaff9b957f1e6c04f9bfd9b8e0eb84f78bbf8
- 923de0afd9a7e717a5e1d25747caf4840633db25
- a487ce76e86940c94801da6157bcf70ed4005c1f
- bd3ebdbd135b30946fdf55b41b5e96497d0c3e4a
2014-10-13 17:21:33 +02:00
Bjoern Schiessle
d71fae51f8 distinguish between file dependent shares and other shares 2014-10-13 17:01:19 +02:00
Vincent Petry
372676ee17 Fixed array detection on public download
When downloading a folder called "0001" PHP should fallback to parsing
it as string and properly detect that it is not a JSON array.

Backport of 6cbabdf217 from master
2014-10-13 16:56:37 +02:00
Lukas Reschke
686a43ccf3 force loading of encryption app 2014-10-13 11:52:16 +02:00
Jörn Friedrich Dreyer
3ce828b4b7 make trashbin compatible with objectstore, replace glob with search in cache, make unknown free space work like unlimited free space 2014-10-10 18:26:43 +02:00
Vincent Petry
b297ddf349 Log warning when no uid was found for user
In some incomplete setups (like mine) it can happen that the uid
attribute of users is missing.

To be able to find out that something is wrong, a debug message is now
logged when it has not been found.

Backport of 59f9107dd9 from master
2014-10-10 17:59:05 +02:00
Bjoern Schiessle
ecc7161611 make sure that we always delete oldest first 2014-10-10 17:31:27 +02:00
Bjoern Schiessle
f4c91f0e82 try to get path from filesystem 2014-10-10 15:58:13 +02:00
Bjoern Schiessle
5a37703b3f fix performance issues 2014-10-10 15:58:00 +02:00
Vincent Petry
088879c4ec Prevent button click when enter key is pressed in LDAP wizard
Pressing enter in the LDAP wizard will trigger a click on the first
button. In the main page it would trigger the delete dialog, which is
quite inconvenient.

Added a type attribute to suppress this behavior.

Backport of bb424802c8 from master
2014-10-10 14:58:06 +02:00
Lukas Reschke
7097201eab Merge pull request #11503 from owncloud/stable7-9753
Stable7 9753
2014-10-10 13:02:28 +02:00
Lukas Reschke
b089b85753 Refactor MailSettings controller
- Do not store the password (fixes https://github.com/owncloud/core/issues/11385)
- Refactor to AppFramework
- Add unit tests
2014-10-10 12:34:43 +02:00
Tony Zelenoff
34c1760e9f Urlencode file name before passing it to cURL
Large file helper use cURL to determine file sizes. Thus filenames must be
urlencoded in case special symbols like '#' can cause BadRequest errors.

Signed-off-by: Tony Zelenoff <antonz@parallels.com>

Backport of 2d03019c91 from master
2014-10-10 10:58:26 +02:00
Vincent Petry
140f3a7aa9 Merge pull request #11505 from owncloud/stable7-ext-updateetagmount
Stable7 ext updateetagmount
2014-10-10 09:54:33 +02:00
Morris Jobke
f1a99ebb6d Additional changes to config.sample.php and typo fixes 2014-10-10 01:51:57 +02:00
Morris Jobke
bd205c58c7 apply @carlaschroder's changes from owncloud/documentation#594 2014-10-10 01:50:56 +02:00
Vincent Petry
a036cc56c4 Added PHP docs for etag propagator 2014-10-09 18:20:25 +02:00
Robin Appelman
1203580848 More phpdoc 2014-10-09 18:20:20 +02:00
Robin Appelman
f534574b30 Hookup the etag propagator 2014-10-09 18:20:15 +02:00
Robin Appelman
a11a75faba Add EtagPropagator to handle etag changes when external storages are changed 2014-10-09 18:20:11 +02:00
Robin Appelman
0d8d5cb3e6 Fix add/remove mountpoint hooks 2014-10-09 18:20:07 +02:00
Robin Appelman
4858b5e94a Expose getAppKeys trough \OCP\IConfig 2014-10-09 18:19:59 +02:00
Vincent Petry
3d68d172af Added failing unit tests for mount config hooks 2014-10-09 18:18:50 +02:00
Vincent Petry
fe652258be Added filesystem hooks for mount/unmount ext storage 2014-10-09 18:18:46 +02:00
Vincent Petry
24dbdd2b8e Clear enabled apps cache after loading authentication app
Since getEnabledApps() depends on an authentication app to be loaded,
especially in the case of LDAP, the cache from getEnabledApps() is now
cleared to make sure that subsequent calls will properly return apps
that were enabled for groups.
This is because getEnabledApps() uses the inGroups() function from the
group manager provided by LDAP or any other authentication app.

Backport of 36d22825e0 from master
2014-10-09 17:19:34 +02:00
Lukas Reschke
f8675f60e8 Add beforeeach and aftereach 2014-10-09 18:04:56 +03:00
Lukas Reschke
d6ee378564 Add support for keys in the info.xml
This allows to have links to different doc base URLs
2014-10-09 17:00:06 +02:00
Lukas Reschke
335ad56ff8 Add unittest for filePath 2014-10-09 17:59:58 +03:00
Robin Appelman
7479a6ebf9 Remove special case for css in OC.filePath 2014-10-09 17:59:35 +03:00
Arthur Schiwon
f0c4347330 Backport of #10527
properly cancel a Paginated Results operation in order to avoid protocol errors, fixes #10526

abandon ongoing paged search before starting a new one

abandond paged search only if PHP supports them

init a new paged search on read operations to satisfy OpenLDAP

make scrutinizer happy, very minor changes
2014-10-09 14:29:00 +02:00
Bjoern Schiessle
d222d2b73f check if I can create a file at the location 2014-10-09 13:30:11 +02:00
MTRichards
5b6f866756 Updated info.xml app description
Made description consistent with other apps
2014-10-08 22:34:10 -04:00
MTRichards
cdeb7cb9b3 Updated info.xml app description
Updated description
2014-10-08 22:33:02 -04:00
MTRichards
a7398bcfa8 Updated info.xml app description
Updated app description
2014-10-08 22:28:11 -04:00
MTRichards
f4e1e5f414 Updated info.xml app description
Backported to stable 7
2014-10-08 22:15:33 -04:00
MTRichards
c9875e3959 Updated info.xml app description
Updated documentation. Removed doc links as this has two packages, need to not hard link.
2014-10-08 22:14:07 -04:00
MTRichards
a3c6b7a662 Updated info.xml app description
Backporting to stable 7. Removed doc links as this is packaged in 2 different setups, links are different.
2014-10-08 22:10:32 -04:00
Morris Jobke
063d2032a7 style fixes in config.sample.php 2014-10-08 23:27:57 +02:00
Morris Jobke
47c05e048f Fixes in config.sample.php
* owncloud -> ownCloud
* add default value
* proper comment style
* fix line wrapping and minor typo
* remove duplication
* add Vincents proposals
* drop master config switches while backporting
2014-10-08 23:24:34 +02:00
Morris Jobke
c83a17e6c8 re-arrange the config options and sort them in a semantical way
Conflicts:
	config/config.sample.php
2014-10-08 23:21:58 +02:00
Morris Jobke
3e7bc72281 unify style of config.sample.php
* use ' instead of " for config option
* place default parameters where useful into sample
* use proper comment block
* limit line size to 80 characters
2014-10-08 23:20:31 +02:00
brumsoel
251e4d78b8 Fix file size comparator return value 2014-10-08 23:09:01 +02:00
Vincent Petry
6b89f4b44d Merge pull request #11375 from owncloud/fix_files_external_amazon_s3_stat_call
Fix files external amazon s3
2014-10-08 18:49:41 +02:00
Bjoern Schiessle
a766f5b633 improved visual feedback if user enabled recovery key 2014-10-08 16:20:21 +02:00
Bjoern Schiessle
7ee3ad411c improved visual feedback if recovery key password gets changed 2014-10-08 16:19:28 +02:00
Bjoern Schiessle
bdfd3ca2bf improved visual feedback if recovery key gets enabled/disabled 2014-10-08 16:18:59 +02:00
Bjoern Schiessle
4dcd1ed34f make sure that the notification gets shown again after a second try 2014-10-08 16:13:35 +02:00
michag86
a045f8cd3a restrict $div to local scope 2014-10-07 16:30:21 +02:00
michag86
4e74983676 variable for found avatar div 2014-10-07 16:30:21 +02:00
michag86
aedd64b5a6 fix for issue #10483 2014-10-07 16:30:21 +02:00
Vincent Petry
b6e81b517f Merge pull request #11457 from owncloud/stable7-fix-rename-position
fix position of rename field, fix #10867
2014-10-07 16:00:41 +02:00
Jan-Christoph Borchardt
77b5e2e661 fix position of rename field, fix #10867 2014-10-07 14:58:01 +02:00
Jan-Christoph Borchardt
9411a6ea53 reduce bottom padding in filelist, fix perceived glitching, fix #11213 2014-10-07 14:52:22 +02:00
Victor Dubiniuk
e0d1fa554f Add CssImportFilter. More elegant fix for documents#348 2014-10-07 14:33:42 +02:00
Jan-Christoph Borchardt
532f65116a fix position and style of sort indicator 2014-10-07 14:23:27 +02:00
Bjoern Schiessle
30398370b8 we also encrypt/decrypt files in the versions folder for previews and if encryption is enabled/disabled 2014-10-06 13:11:09 +02:00
Jörn Friedrich Dreyer
413bd5406c extract batchDelete(), better comments 2014-10-06 11:08:21 +02:00
André Gaul
056b97834d files_extern: remove empty Body and ContentLength in Amazon S3 mount
fixes #10501

Conflicts:
	apps/files_external/lib/amazons3.php
2014-10-02 12:00:50 +02:00
Lukas Reschke
1f5f278501 Merge pull request #11299 from owncloud/barkport-10934
[stable7] Don't complain about non-writable datadirs before we're installed
2014-10-02 10:38:15 +02:00
Jörn Friedrich Dreyer
2b91e628f5 fix amazon s3 issues
folder size and mtime is always unknown in s3

more s3 fixes

make rescanDelay of root dir configurable, add on the fly update of legacy storage ids, !isset -> empty when checking strings

reduce number of http calls on remove and rmdir

fix typo
2014-10-01 14:38:50 +02:00
Jörn Friedrich Dreyer
c8dab5829c make some storage tests explain what went wrong 2014-10-01 14:38:50 +02:00
Jörn Friedrich Dreyer
8089e0f9b8 strict comparison 2014-10-01 14:38:50 +02:00
Jörn Friedrich Dreyer
7fd925e96d log exceptions when listing files 2014-10-01 13:17:28 +02:00
Robin Appelman
3a4ac2caa1 Skip test for unsuported feature for amazon s3 2014-10-01 13:15:20 +02:00
Robin Appelman
67b9edeeb7 Fix copy overwrite for amazon s3 2014-10-01 13:15:13 +02:00
Robin Appelman
25d003a52d Fix amazon s3 rename overwrite 2014-10-01 13:15:06 +02:00
Robin Appelman
a47f965b87 Fix AmazonS3 rmdir on the root 2014-10-01 13:14:56 +02:00
Lukas Reschke
0484939fe0 Redirect only to absolute URL
We do not want to redirect to other domains using the "?redirect_url=" feature. Please notice, that the ownCloud project does not consider open redirectors as security issue.
2014-10-01 12:49:54 +02:00
Lukas Reschke
69296befa6 Use SVG previews for public sharing
Fixes https://github.com/owncloud/core/pull/11367#issuecomment-57306037 and https://github.com/owncloud/core/issues/9218
2014-10-01 11:16:10 +02:00
Lukas Reschke
bc5c608e4e Add flock to config
This adds a file lock to the config in hope that this prevents race conditions as reported in https://github.com/owncloud/core/issues/11070

Testplan:

- [ ] Delete config.php and make it read-only => Error is thrown that it is not writeable
- [ ] Installation still works
- [ ] Changing config settings works (i.e. using the SMTP config switches in the administration menu)
- [ ] Your PC didn't blow up
- [ ] Installing the news app and the "Disable AppCode checker" app did not destroy your installation

Only skip the main config

Otherwise read only additional configs might not be processed

Test on tmpdir
2014-10-01 11:14:15 +02:00
Bjoern Schiessle
02374e595a don't clean up the test environment if a data provider was finished 2014-09-30 12:50:21 +02:00
Lukas Reschke
f788dffce8 Try to create datadirectory to test whether the .htaccess works
Fixes https://github.com/owncloud/core/pull/11299#issuecomment-56968588 and also https://github.com/owncloud/core/issues/10628 together with https://github.com/owncloud/core/pull/11299#issuecomment-56968588 when backported.
2014-09-30 10:24:52 +02:00
Robin Appelman
d102cb4ffa Don't test for htaccess if we cant write into the datadir anyway 2014-09-30 10:24:52 +02:00
Robin Appelman
83a06950f4 Check for writable datadir during setup 2014-09-30 10:24:52 +02:00
Robin Appelman
6c09c0e082 Don't complain about non-writable datadirs before we're installed
Conflicts:
	lib/private/util.php
2014-09-30 10:24:52 +02:00
Robin Appelman
2376d69115 Inject config into checkserver and cleanup tests
Conflicts:
	lib/private/util.php
2014-09-30 10:24:52 +02:00
Lukas Reschke
7eb0d6866c Close the session for preview generation
Without closing the session every preview image generation is locking the session which makes the webinterface unresponsive.
2014-09-29 22:03:58 +02:00
Arthur Schiwon
5ddacaed7f dn needs to be fetched to be able to detect memberOf support 2014-09-29 15:17:34 +02:00
Dan Bartram
bd74ab2886 Clear up confusion of log_rotate_size config value
Remove the second comment which could be confused with suggesting an invalid value: `// 100 MiB`.

To keep the easily readable example value, it has been moved into the comment header for the config item.
2014-09-29 09:39:55 +02:00
Frank Karlitschek
424cc31fd6 increase version 2014-09-28 13:19:53 +02:00
Björn Schießle
d388ef5c0a Merge pull request #11363 from owncloud/sharing_group_shares_stable7
Sharing group shares stable7
2014-10-01 23:35:33 +02:00
Thomas Müller
7d389b26f9 catch and log exception in OC_Helper::cleanTmp() 2014-10-01 15:57:30 +02:00
Bjoern Schiessle
9254073786 some small fixed, suggested by scrutinizer 2014-10-01 15:13:48 +02:00
Bjoern Schiessle
bb703006da throw a exception if we can't handle the provided path 2014-10-01 15:13:48 +02:00
Bjoern Schiessle
1371fecf26 on unshare only unshare childrens if there is no other parent available 2014-10-01 15:13:48 +02:00
Bjoern Schiessle
1e5c786392 only create a new share entry, if the user needs a different target name than the group share 2014-10-01 15:13:48 +02:00
Bjoern Schiessle
6d85cf2a0b for group shares we don't need a extra db entry of groupTarget equals itemTarget 2014-10-01 15:13:48 +02:00
Bjoern Schiessle
3b1715c3fc for group shares only the parent has the up-to-date permission. Make sure that we always use this permission, except if the user permission is '0' because in this case the user unshared the group share from self 2014-10-01 15:13:48 +02:00
Bjoern Schiessle
6d747e9721 call \OCP\Share::getItemsSharedWithUser() to get exclude list, this way all checks are executed, e.g. to check if the share is really visible 2014-10-01 15:13:48 +02:00
Bjoern Schiessle
c5db1ef3cc always select permissions, used in getItems() 2014-10-01 15:13:48 +02:00
Bjoern Schiessle
27a630b0bc some small fixes 2014-10-01 15:13:48 +02:00
Bjoern Schiessle
e25593db4e clear share table after each test run 2014-10-01 15:13:47 +02:00
Bjoern Schiessle
7edf912ef8 only add a new row if it isn't a unique share, otherwise update the existing row 2014-10-01 15:13:47 +02:00
Bjoern Schiessle
aab44694ad fix unit tests 2014-10-01 15:13:47 +02:00
Bjoern Schiessle
98268078c0 mark exclude list as deprecated. It neither used by the files app nor by the
calendar or contacts app. It doesn't make sense to build a exclude list by the
share API, the apps knows best which are valid targets.
2014-10-01 15:13:47 +02:00
Bjoern Schiessle
df97d8299a fix add user to group to work with grouped shares 2014-10-01 15:13:47 +02:00
Bjoern Schiessle
97dba2e022 generateTarget() will always find a unique target 2014-10-01 15:13:47 +02:00
Bjoern Schiessle
b52154b306 unit tests for grouping of shares pointing to the same source 2014-10-01 15:13:47 +02:00
Bjoern Schiessle
64a9315b4a group shares and combine permissions 2014-10-01 15:13:47 +02:00
Lukas Reschke
3fb55d9d2e Merge pull request #11364 from owncloud/fix_unit_test_oc7
make sure that the users file system is initialized before we emit the post hook
2014-10-01 15:07:16 +02:00
Bjoern Schiessle
094f5bcc78 make sure that we re-load the file system after the quota was set 2014-10-01 09:42:50 +02:00
Bjoern Schiessle
79f5346105 make sure that the users file system is initialized before we emit the post hook 2014-09-30 13:14:04 +02:00
Vincent Petry
5c0139fdff Prevent monkey clicking on labels
Clicking on labels while the spinner is there will not trigger the
hidden checkbox any more.
2014-09-27 10:42:00 +02:00
Lukas Reschke
c1c70b306e Add ID to markup to have the checkboxes in the right position 2014-09-27 10:41:51 +02:00
Vincent Petry
4d261d9e15 Added spinners in share dropdown
Added spinners for the following actions:
- adding user
- removing user
- changing password
- toggling allowing upload
- setting a password
2014-09-27 10:41:45 +02:00
Jan-Christoph Borchardt
257dbe61e2 for whitelabeled edition, show branding name (e.g. 'ownCloud') instead of appname in header bar 2014-09-27 10:29:20 +02:00
Robin Appelman
d660aab116 Fix dav permissions for folders without create permissions 2014-09-26 09:24:24 +02:00
Robin Appelman
e37f44e56a Expose creatable permissions trough fileinfo 2014-09-26 09:24:23 +02:00
Robin Appelman
bdc26c6749 Don't keep the full info of all children in memory 2014-09-25 20:04:02 +02:00
Lukas Reschke
6d8fdcd7d2 Clarify possible preview providers for type Office
a

Conflicts:
	lib/private/preview.php
2014-09-25 00:00:40 +02:00
Morris Jobke
0044c3bb6b Merge pull request #11245 from owncloud/backport-11211
Add a configuration switch for enabled preview mimetypes
2014-09-24 23:57:23 +02:00
Robin Appelman
1ec756d125 Show a proper error message when trying to scan the filesystem for a non existing user 2014-09-24 23:21:26 +02:00
Thomas Müller
c31990e936 Updating 3rdparty - contains updated tar archive and getid3 2014-09-24 14:30:00 +02:00
Jan-Christoph Borchardt
4e850128e6 fix filepicker home icon being partly hidden, fix #10169 2014-09-24 02:14:20 +02:00
Lukas Reschke
00bb0e122f Check for blacklisted characters
Fixes https://github.com/owncloud/core/issues/11264

(This should in future get moved to the mountpoint class - but that is something for @icewind1991 ;-))
2014-09-24 00:38:18 +02:00
Vincent Petry
2013404e0a Moved WebDAV and internet checks to client side JS
- Added setup checks in JavaScript
- Moved isWebDAVWorking to JS using SetupChecks
- Moved internet connection checks to an ajax call that goes through the
  server

Backport of e65ceb08fc from master
2014-09-23 16:58:19 +02:00
Lukas Reschke
734112a5c1 Merge branch 'stable7' into backport-11211
Conflicts:
	config/config.sample.php
2014-09-23 14:48:16 +02:00
Remco Brenninkmeijer
fbcd80ac08 Previous commit was not based on master, retry. Removed broken tar cutter, double extensions are not possible in temp files. Added tar support. Fixed extension switch. 2014-09-23 14:45:56 +02:00
Thomas Müller
435b9c3028 using flush() here is pointless as we render the layout into a memory buffer and actually transmit the data later 2014-09-23 13:41:45 +02:00
Thomas Müller
838c849a5a remove post setup check
Backport of 21412559df from master
2014-09-23 13:00:55 +02:00
Vincent Petry
85d7492398 Fix share key finding algorithm in various cases
Instead of inaccurate pattern matching, use the list of users who we
know have access to the file to build the list of share keys.

This covers the following cases:

- Move/copy files into a subfolder within a share
- Unsharing from a user
- Deleting files directlry / moving share keys to trashbin

Backport of 1e631754d7 from master
2014-09-23 12:54:07 +02:00
Victor Dubiniuk
a6eb638380 Use non-empty defaults 2014-09-23 12:53:11 +02:00
Lukas Reschke
cb3bc5ad31 Do only follow HTTP and HTTPS redirects
We do not want to follow redirects to other protocols since they might allow an adversary to bypass network restrictions. (i.e. a redirect to ftp:// might be used to access files of a FTP server which might be in a secure zone and not be reachable from the net but from the ownCloud server)

Get final redirect manually using get_headers()

Migrate to HTTPHelper class and add unit tests

Conflicts:
	apps/files/ajax/newfile.php
	lib/private/files/storage/dav.php
	lib/private/server.php
	lib/private/util.php
	lib/public/iservercontainer.php
2014-09-23 11:51:18 +02:00
Lukas Reschke
d9384cd959 Append port to trusted domain in case it's not 80 or 443
Ref https://github.com/owncloud/core/pull/10584#issuecomment-54677059

Backport of 2590a4dc85 from master
2014-09-23 11:20:52 +02:00
Bjoern Schiessle
bd7f6c9e18 first check if a private key exists, if not it is always a recovery szenario 2014-09-23 10:42:56 +02:00
Lukas Reschke
bb69eebde4 Add a configuration switch for enabled preview mimetypes
Backport of https://github.com/owncloud/core/pull/11211 to stable7
2014-09-23 10:42:45 +02:00
Vincent Petry
cb39c55073 WebDAV now throws 403 when deletion did not work
Assume a permission issue whenever a file could not be deleted.

This is because some storages are not able to return permissions, so a
permission denied situation can only be triggered during direct
deletion.
2014-09-23 00:50:28 +02:00
Morris Jobke
33737f9526 Merge pull request #11230 from owncloud/fix-failing-mountconfig-tests-stable7
Load apps in test situations
2014-09-23 00:45:35 +02:00
Victor Dubiniuk
b3ddd39438 Log unsuccessful temp file creation and return false 2014-09-22 18:57:03 +03:00
Vincent Petry
2b297567b5 Added extra check to avoid deleting key folders
Whenever a delete operation is called twice in a row, it could happen
that the first call already deleted the file.

The second call would return an empty $ownerPath because the file does
not exist. That empty $ownerPath would run the key deletion operation on
the wrong path.

This fix adds checks in many places to make sure we don't use $ownerPath
when it's empty or null.

Backport of 8aca127e52 from master
2014-09-22 17:06:46 +02:00
Thomas Müller
d6276beac0 date printed in the admin section regarding last execution time is already displayed in the users timezone - no need to append UTC 2014-09-22 16:58:17 +02:00
Vincent Petry
03bb3d2bdc Reenable file proxy when renaming between mount points
When moving a folder into another mount point, $renamedFiles is empty
because that goes over a different mechanism.

In such case, this fix makes sure that the file proxy is reenable to
avoid breaking the subsequent files that are being moved.
2014-09-22 16:30:37 +02:00
Jörn Friedrich Dreyer
44c7562c7b in quota wrapper use === instead of ! for better readability and as in other wrappers 2014-09-22 16:26:20 +02:00
Jörn Friedrich Dreyer
e556698db7 return boolean in Ciose::stream_seek 2014-09-22 16:26:14 +02:00
Jörn Friedrich Dreyer
2bdf5a8b1a return boolean in OC::stream_seek 2014-09-22 16:26:01 +02:00
Jörn Friedrich Dreyer
dfc6bfe131 add seek and tell to streamwrapper test 2014-09-22 16:25:50 +02:00
Robin McCorkell
c0d1b6bfb2 Load apps in test situations 2014-09-22 15:39:16 +02:00
Vincent Petry
387638e317 Use afterBind to send fileId header for files and directories
afterBind is called for both files and directories and is now used to
send the OC-FileId headers.
2014-09-22 14:12:52 +02:00
Morris Jobke
2d3ea08e19 update 3rdparty submodule to its stable7 commit 2014-09-22 13:05:36 +02:00
voxsim
3ebd0b8983 Backport of #9225
fix in displayNamesInGroup: when specified limit N, we did complex search only in the first N users

change logic in displayNamesInGroup and add some unit tests

add more logic in displayNamesInGroup for big user bases

1. remove sizeof($filteredUsers) > 0 as condition
2. use count instead of sizeof. Latter is an alias to first one, practically we stick to count everywhere. Having it consistent helps with readability.
3. move whitespace so we have $groupUsers[] = $filteredUser; instead of $groupUsers []= $filteredUser;
2014-09-22 12:21:34 +02:00
Robin Appelman
984b6a28ac Also setup the filesystem when matching routes 2014-09-22 10:34:40 +02:00
Robin Appelman
744b20bc0d Remove unneeded file initialization in encryption, already handled in a hook 2014-09-22 10:34:37 +02:00
Robin Appelman
8905e77d60 Don't automatically setup the filesystem the moment we load OC\Files\FileSystem 2014-09-22 10:34:32 +02:00
Vincent Petry
c16c680e32 Add select2 cache for complete group list
To avoid making a server request every time the dropdown opens, the
whole list of groups are cached (from the last request):

Whenever the user types in a search term it will still send server
requests.
2014-09-22 10:14:25 +02:00
Jan-Christoph Borchardt
13cee302f7 fit select2 to ownCloud input style 2014-09-22 10:14:22 +02:00
Vincent Petry
2aaad09062 Fixed select2 for admin and apps page
Added explicit escaping.
Now internally using a pipe symbol as separator for select2, to make it
possible to use group names containing commas.
2014-09-22 10:14:18 +02:00
Vincent Petry
996c68aa2e Added select2 on the apps page
Moved setupGroupsSelect() from admin.js to a common settings.js
as OC.Settings.setupGoupsSelect().

Now using select2 as well on the apps page.
2014-09-22 10:14:15 +02:00
Vincent Petry
7f030eedde Remove excludegroup.php
The ajax call is now using ajax/appconfig.php instead
2014-09-22 10:14:11 +02:00
Vincent Petry
2e93300313 Load select2 on admin page 2014-09-22 10:14:08 +02:00
Vincent Petry
a385a367b0 Now using select2 for the groups excluded from sharing 2014-09-22 10:14:05 +02:00
Vincent Petry
97b83b9098 Move select2 from files_external to core
Backport of 412da87e65 from master
2014-09-22 10:13:46 +02:00
Bjoern Schiessle
ce69e83b33 make sure that we really catch the files folder only 2014-09-22 10:00:47 +02:00
Bjoern Schiessle
a2e9056832 create backup from all keys before recovery 2014-09-22 10:00:37 +02:00
Thomas Müller
260229b027 Merge pull request #11202 from owncloud/stable_l10n_backport
backport server2server strings
2014-09-22 09:16:34 +02:00
Thomas Müller
0c014409e6 Merge pull request #11201 from owncloud/fix-button-text
fix button text l10n
2014-09-22 09:16:00 +02:00
Georg Ehrke
2068eaf16b make sure preview prop is instanceof OC_Image before using it in showPreview 2014-09-22 00:48:01 +02:00
Georg Ehrke
645e7a0c9e delete old previews 2014-09-21 22:49:22 +02:00
Volkan Gezer
0afd79a22c backport server2server strings 2014-09-21 17:34:39 +02:00
Volkan Gezer
6946962609 fix button text l10n 2014-09-21 17:32:37 +02:00
Morris Jobke
f8ecef2917 Merge pull request #11195 from owncloud/stable_l10n_backport
backport stable translations
2014-09-21 11:16:08 +02:00
Thomas Müller
6c544b95e8 adding 'smallint unsigned' to type mapping for sqlite 2014-09-21 11:14:14 +02:00
Volkan Gezer
5637a581d2 backport stable translations 2014-09-20 18:50:17 +02:00
Robin McCorkell
83b9a89569 Merge pull request #11190 from owncloud/fix-unit-test-sort-order-stable7
Don't rely on the sorting the database gives us for tests
2014-09-20 12:09:50 +01:00
Robin Appelman
8580e2a697 Don't rely on the sorting the database gives us for tests 2014-09-20 09:17:18 +02:00
Lukas Reschke
7f45226b8e Prevent updates between multiple major versions
Ref https://github.com/owncloud/core/issues/11078
2014-09-19 17:00:50 +02:00
Lukas Reschke
37632e428d Move BasicAuth check to "isLoggedIn()"
Ensures that Basic Auth works properly for APIs and removes the need for some even uglier lines of code.
2014-09-19 13:41:55 +02:00
Volkan Gezer
7930440b31 update encryption doc link
I don't know how we missed to update this :/

update ldap doc link
2014-09-18 18:20:20 +02:00
Lukas Reschke
749c519759 Move basic auth check
At the previous point not all apps were initialized. Now the basic auth check happens together at the same location as all others.

Fixes https://github.com/owncloud/core/issues/11129
2014-09-18 14:41:43 +02:00
Vincent Petry
35cb14c95f Add timeout to user and group deletion notification
Added timeout in DeleteHandler to auto-delete after a delay.

Fixed issue where OC.Notification.hide() was called twice in a row when
deleting multiple entries, causing the second notification to disappear.
Fixed issue where "undo" click event handler was registered multiple
times when calling setNotifications() twice.
Added JS unit tests for the DeleteHandler class.

Refix undo users, groups feature

Timeout is now cleared in cancel().

Fixed click handler name for "undo" to be able to work with multiple
DeleteHandler instances (in our case one for users and one for groups)
so that there is no conflict.

Backport of 0d9f24a0ef from master
2014-09-18 10:22:10 +02:00
Lukas Reschke
28f462095d Make 404 page easier to understand
Fixes https://github.com/owncloud/core/issues/11133
2014-09-18 09:10:56 +02:00
Frank Karlitschek
19a7aa081e version bump 2014-09-13 22:56:07 +02:00
Thomas Müller
82ccb04182 adding unit test to truncate the table 2014-09-17 14:28:31 +02:00
Andreas Fischer
ebbd40385a Use Doctrine Platform to generate SQL query for table truncation. 2014-09-17 14:28:31 +02:00
Vincent Petry
03f9b14a23 Merge pull request #11053 from owncloud/baskport-11041-stable7
content size checks are not valid for LOCK
2014-09-16 16:46:47 +02:00
Lukas Reschke
8ed1762dfb Reword the description
The old one was just horrible wrong.
2014-09-16 16:01:35 +02:00
Thomas Müller
76ff7ab007 Disable database migrations for MSSQL - scripts have to be applied manually 2014-09-16 14:03:24 +02:00
Bjoern Schiessle
9bf1a18512 also expire file if timestamp = limit, happens if trashbin_retention_obligation is set to zero 2014-09-16 13:58:56 +02:00
kondou
86400ea4c8 Preserve transparency when loading from a file
Fix #7148 - again :)
2014-09-16 08:34:18 +02:00
Lukas Reschke
ff6deb809a Use secure mimetype for content delivery
Adds some hardening against potential CSP bypassed.
2014-09-15 12:50:35 +02:00
Vincent Petry
7f1416e62a Remove passing by reference to allow for proper GC
The garbage collector in PHP 5.3.10 does not properly release the file
handle when calling fclose() due to the fact that it is passed by
reference.
This has the side-effect of preventing file locks to be released as well
when the files_locking app is enabled.

This fix removes the useless passing by reference and now the file
handle and file lock are freed properly.
2014-09-15 11:58:18 +02:00
Joas Schilling
a23396452f Set overwritewebroot when installing owncloud to avoid problems
Backport of 249558966e to stable7
2014-09-15 11:06:47 +02:00
Arthur Schiwon
1981d220c5 if only one attribute is requested, the returned array has 0 as key instead of attribute name. fixes #10888 2014-09-15 11:01:21 +02:00
Lukas Reschke
572720fb7a Do not double decode values
Fixes https://github.com/owncloud/core/issues/11012
2014-09-15 07:59:04 +02:00
Thomas Müller
0732131cd9 content size checks are not valid for LOCK 2014-09-12 22:06:28 +02:00
Thomas Müller
2ffcff228c content size checks are not valid for LOCK 2014-09-12 21:53:28 +02:00
Jan-Christoph Borchardt
bec08955b2 fix nojavascript message alignment 2014-09-11 23:06:45 +02:00
Johan Björk
f040529e6d Converted an array to PHP5.3 compatible version 2014-09-11 18:01:11 +02:00
Johan Björk
5ac620cb04 Fixes #8326: deletion of directories on S3 2014-09-11 18:01:01 +02:00
Jan-Christoph Borchardt
e26a5ff715 fix folder icon alignment, fix #10866 2014-09-11 17:07:52 +02:00
Jan-Christoph Borchardt
b7c7ec26c7 fix svg of calendar filetype icon 2014-09-11 17:07:46 +02:00
Jan-Christoph Borchardt
7f1e32764d fix size of toggle icon, make container square 2014-09-11 17:07:40 +02:00
Jan-Christoph Borchardt
3f5cefb07f improve style of checkmark icon, less bold, fitting ownCloud style 2014-09-11 17:07:34 +02:00
Jörn Friedrich Dreyer
463ad5a50d repair search lucene before installing 2014-09-11 14:24:11 +02:00
Lukas Reschke
ec9517f98b Add X-UA-Compatible to all templates
Replaces https://github.com/owncloud/core/pull/10850
2014-09-11 12:39:45 +02:00
Vincent Petry
36680a5a81 Merge pull request #10998 from owncloud/use-section-in-form
Use section in first element in user_ldap
2014-09-11 12:07:31 +02:00
Lukas Reschke
04597aedcb Use section in first element 2014-09-10 18:47:29 +02:00
Vincent Petry
1a9e08ae4d Merge pull request #10973 from owncloud/stable7-issue/10847
[Stable7] Use correct language package so the subject is correctly translated
2014-09-10 14:57:32 +02:00
Vincent Petry
dedb47390b Do not load extra user backends when an upgrade is due
Whenever an upgrade is due, do not load extra user backends

Backport of d6bfd90bf8 from master
2014-09-10 10:42:56 +02:00
Lukas Reschke
95eb9bf8ae Merge pull request #10884 from owncloud/stable7_backport_issue_10674
Backport issue 10764
2014-09-10 10:31:26 +02:00
Joas Schilling
cc0a855103 Use correct language package so the subject is correctly translated
Fix #10847

Conflicts:
	lib/private/share/mailnotifications.php
2014-09-09 23:44:15 +03:00
Joas Schilling
19dd866c7e Fix broken new lines in plain text mail template 2014-09-09 23:40:56 +03:00
Bernhard Posselt
8e89c51e87 more sugar for including lists of templates 2014-09-09 19:40:56 +02:00
Bernhard Posselt
2a859f84a8 append .html since componets always use html files 2014-09-09 19:40:39 +02:00
Bernhard Posselt
8869dcf1af add template functions for html imports 2014-09-09 19:40:18 +02:00
Bernhard Posselt
ca121d8438 add shortcut functions for style and script 2014-09-09 19:40:02 +02:00
Jörn Friedrich Dreyer
f5bac5fb2d make objectstore tests check fileid on rename 2014-09-09 10:27:34 +02:00
Jörn Friedrich Dreyer
c52f66cc73 Keep fileid on move in objectstore, fixes #10848 2014-09-09 10:27:21 +02:00
Jörn Friedrich Dreyer
5e5d9e0a80 allow . in dbname on web install 2014-09-09 09:43:58 +02:00
Lukas Reschke
b98c22d39b Don't show warning when has_internet_connection is set to false
Revert

Add disabled
2014-09-09 09:20:03 +02:00
Jan-Christoph Borchardt
b609d36e3c also add no-JavaScript notice to log in and sharing pages because they do not work without JS either 2014-09-08 22:01:56 +02:00
Jan-Christoph Borchardt
a7968604d8 fix styling and wording of no-JavaScript message 2014-09-08 22:01:49 +02:00
Clark Tomlinson
9b99c81a9d Using countUsers method to return true count of users 2014-09-08 18:52:14 +02:00
Lukas Reschke
96bb1f7f45 Check for admin user instead of subadmin 2014-09-08 18:25:23 +02:00
Vincent Petry
85cde16feb Catch exceptions when moving files
When moving files on storages that don't expose permissions, the storage
itself might throw an exception when the permission is denied.

This fix ensures that exceptions are caught and forwarded to the client
instead of just hanging.

Backport of e43c9b84c4 from master
2014-09-08 16:26:01 +02:00
Oliver Kohl D.Sc
1e4431ba01 Update cron.php 2014-09-08 09:54:23 +02:00
Oliver Kohl D.Sc
994f36e498 CRON call ends in null exception
[error] 4461#0: *186285 FastCGI sent in stderr: "PHP message
: PHP Fatal error:  Call to a member function execute() on null in /var/www/ownc
loud/cron.php on line 125" while reading response header from upstream, client:
217.13.183.252, server: cloud.mycloud.com, request: "GET /cron.php HTTP/1.1", upstre
am: "fastcgi://unix:/var/run/php5-fpm.sock:", host: "cloud.mycloud.com", referrer: "
https://cloud.mycloud.com/"
2014-09-08 09:54:18 +02:00
Lukas Reschke
640abbe099 Move trusted domain check to init()
handleRequest() is not called from remote.php or public.php which made these files party available but all included apps in there produced errors.

As the expected behaviour is anyways that a trusted domain warning is shown I moved this to init()

Fixes https://github.com/owncloud/core/issues/10064
2014-09-05 18:20:40 +02:00
Jörn Friedrich Dreyer
1f2550e3da add / to url to match route 2014-09-05 16:55:36 +02:00
Vincent Petry
cfca88f20e Scroll to new file/folder after adding
When creating a new file from the menu, the list now scrolls to that
file.
2014-09-05 16:55:36 +02:00
Vincent Petry
8e77ca6201 Fixed scrollto for search results
Now passing the "scrollto" URL argument to the file list class which
will also automatically scroll and highlight the matching element.

This code is triggered by the search box when in a different folder and
also by the activity app.
2014-09-05 16:55:36 +02:00
Lukas Reschke
41d8157937 Add isAppstoreEnabled instead of hijacking the URL 2014-09-05 15:14:29 +02:00
Lukas Reschke
6534304196 Remove different URL for EE
This can now be achieved by setting `appstoreenabled` to `false` in config.php
2014-09-05 15:14:19 +02:00
Jesus Macias
7bd4f6cac5 Backport issue 10764 2014-09-05 13:23:09 +02:00
Vincent Petry
69cc2044f0 Added permission check for drag and drop
When dropping files onto a read-only folder, a notification
is now shown instead of attempting to upload.

This for both the drag for upload and drag from inside the file list
cases.

Backport of f1bfe35cda from master
2014-09-05 12:21:42 +02:00
Jörn Friedrich Dreyer
eb682a3022 Merge pull request #10878 from owncloud/remove_leftover
remove a left over that uselessly fetches all users
2014-09-05 11:59:12 +02:00
Arthur Schiwon
e638a9a444 remove a left over that uselessly fetches all users 2014-09-05 10:27:34 +02:00
Vincent Petry
f268eee86c Added unit tests for cache of enabled apps 2014-09-04 18:02:58 +02:00
Vincent Petry
28e1480d1c Added test for needUpgrade for core 2014-09-04 18:02:54 +02:00
Vincent Petry
81c4043d61 Fix upgrade process when apps enabled for specific groups
Fix issue where the currently logged user was causing side-effects when
upgrading.
Now setting incognito mode (no user) on update to make sure the whole
apps list is taken into account with getEnabledApps() or isEnabled().
2014-09-04 18:02:49 +02:00
Lukas Reschke
bd5796a0d1 Merge pull request #10558 from owncloud/ajaxify_user_list_for_files_external_stable7
Ajaxify user list for files external stable7
2014-09-04 13:16:52 +02:00
pdessauw
919d19c906 Highlight every uploaded files and scroll down to the last one
Backport of 0d078e48ce from master
2014-09-04 10:45:41 +02:00
Vincent Petry
459c78106d Merge pull request #10823 from oparoz/patch-1
Typo in whichOpenOffice test
2014-09-04 10:20:50 +02:00
Olivier Paroz
444e21ab15 Typ in whichOpenOffice test 2014-09-02 22:11:49 +02:00
Raghu Nayyar
b59d58fdc1 Merge pull request #10789 from owncloud/fix-z-index
fix z-index of navigation sidebar for mobile devices
2014-09-03 01:03:51 +05:30
Volkan Gezer
b26dd3c76b russian translation backport. closes #10759 2014-09-02 17:53:52 +02:00
Morris Jobke
3d35c7edcc fix z-index of navigation sidebar for mobile devices 2014-08-31 19:41:58 +02:00
Morris Jobke
6eb1f7a474 update public interface for getUserFolder 2014-08-31 13:27:53 +02:00
Morris Jobke
f3f1d1ee73 Add optional user ID parameter for getUseFolder 2014-08-31 13:27:53 +02:00
Arthur Schiwon
11b2835cc3 retrieve local users, groups and group members in a sorted way 2014-08-31 11:56:54 +02:00
Jörn Friedrich Dreyer
8b86df308b allow empty hostname and dots in service name for oracle autosetup 2014-08-29 17:11:38 +02:00
Arthur Schiwon
98ac45fa78 Backport of #9214
Wizad: email attribute detection

remove Access as hard dependency, inject it instead

add unit test for mail detection

write log message, if original value was changed

undo falsely changed log file

trigger email detection by Wizard upon any user filter filter change. before it happenend only upon user automatic list filter creation, but not on manual editing

adjust static method vars as well
2014-08-29 15:02:08 +02:00
Robin McCorkell
617636d5a7 Add missing 'personal' mount configuration option 2014-08-29 13:12:36 +01:00
Jan-Christoph Borchardt
4d9fd199ef user mgmt: move 'Show storage location' and 'Show last log in' to settings area 2014-08-29 12:53:17 +02:00
Stephane V
cc820eb824 Adds 2 checkboxes at the top of userlist in the settings, to display/hide optional columns
This fixes #9367.
2014-08-29 12:53:11 +02:00
Jan-Christoph Borchardt
135d40203c define min height/width for icon classes to make sure they show 2014-08-29 12:51:57 +02:00
Jan-Christoph Borchardt
30cc4b9293 allow horizontal scrollbar for personal and admin settings 2014-08-29 10:33:43 +02:00
raghunayyar
04e14ab5a6 Enhancement: Adds input styles to input type dates too. 2014-08-28 17:56:24 +02:00
Arthur Schiwon
a133833fb9 fix wizard test, adjust to changed parameters of the tested method, introduced by 9caa354cfc 2014-08-28 11:39:16 +02:00
Arthur Schiwon
2b001adce0 backport of #8623
set result entry identifier earlier, i.e. before a continue for the same level can happen. otherwise  will always get the same value and we end up in an infinite loop

add unit test to make sure the infinite loop never comes back

Conflicts:
	apps/user_ldap/tests/wizard.php
2014-08-28 11:38:31 +02:00
Lukas Reschke
5a8db83c48 Escape error messages 2014-08-28 09:37:11 +02:00
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
Jörn Friedrich Dreyer
af17de9467 fix deletion of shares 2014-08-21 15:12:06 +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
Jörn Friedrich Dreyer
992bd6dbf2 replace chosen with select2 to provide ajaxified user and group selection for files_external, fixes #7499
remove minified select2 js

show avatars for users, simpler results

remove unneeded users and groups from settings template

fix css, escape user and group names
2014-08-20 19:25:23 +02:00
Jörn Friedrich Dreyer
f74bf923ca add select2 to app specific thirdparty 2014-08-20 19:25:10 +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
708 changed files with 24512 additions and 6356 deletions

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;
}

View File

@@ -32,6 +32,7 @@ try {
OCP\JSON::success(array('data' => $data));
} catch (\OCP\Files\StorageNotAvailableException $e) {
\OCP\Util::logException('files', $e);
OCP\JSON::error(array(
'data' => array(
'exception' => '\OCP\Files\StorageNotAvailableException',
@@ -39,6 +40,7 @@ try {
)
));
} catch (\OCP\Files\StorageInvalidException $e) {
\OCP\Util::logException('files', $e);
OCP\JSON::error(array(
'data' => array(
'exception' => '\OCP\Files\StorageInvalidException',
@@ -46,6 +48,7 @@ try {
)
));
} catch (\Exception $e) {
\OCP\Util::logException('files', $e);
OCP\JSON::error(array(
'data' => array(
'exception' => '\Exception',

View File

@@ -19,10 +19,16 @@ if(\OC\Files\Filesystem::file_exists($target . '/' . $file)) {
if ($target != '' || strtolower($file) != 'shared') {
$targetFile = \OC\Files\Filesystem::normalizePath($target . '/' . $file);
$sourceFile = \OC\Files\Filesystem::normalizePath($dir . '/' . $file);
if(\OC\Files\Filesystem::rename($sourceFile, $targetFile)) {
OCP\JSON::success(array("data" => array( "dir" => $dir, "files" => $file )));
} else {
OCP\JSON::error(array("data" => array( "message" => $l->t("Could not move %s", array($file)) )));
try {
if(\OC\Files\Filesystem::rename($sourceFile, $targetFile)) {
OCP\JSON::success(array("data" => array( "dir" => $dir, "files" => $file )));
} else {
OCP\JSON::error(array("data" => array( "message" => $l->t("Could not move %s", array($file)) )));
}
} catch (\OCP\Files\NotPermittedException $e) {
OCP\JSON::error(array("data" => array( "message" => $l->t("Permission denied") )));
} catch (\Exception $e) {
OCP\JSON::error(array("data" => array( "message" => $e->getMessage())));
}
}else{
OCP\JSON::error(array("data" => array( "message" => $l->t("Could not move %s", array($file)) )));

View File

@@ -93,7 +93,8 @@ if (\OC\Files\Filesystem::file_exists($target)) {
}
if($source) {
if(substr($source, 0, 8)!='https://' and substr($source, 0, 7)!='http://') {
$httpHelper = \OC::$server->getHTTPHelper();
if(!$httpHelper->isHTTPURL($source)) {
OCP\JSON::error(array('data' => array('message' => $l10n->t('Not a valid source'))));
exit();
}
@@ -104,10 +105,36 @@ if($source) {
exit();
}
$ctx = stream_context_create(null, array('notification' =>'progress'));
$source = $httpHelper->getFinalLocationOfURL($source);
$ctx = stream_context_create(\OC::$server->getHTTPHelper()->getDefaultContextArray(), array('notification' =>'progress'));
$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) {

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);

View File

@@ -17,4 +17,7 @@
<webdav>appinfo/remote.php</webdav>
<filesync>appinfo/filesync.php</filesync>
</remote>
<documentation>
<user>user-files</user>
</documentation>
</info>

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

View File

@@ -77,7 +77,11 @@ class Scan extends Command {
if (is_object($user)) {
$user = $user->getUID();
}
$this->scanFiles($user, $output);
if ($this->userManager->userExists($user)) {
$this->scanFiles($user, $output);
} else {
$output->writeln("<error>Unknown user $user</error>");
}
}
}
}

View File

@@ -152,16 +152,24 @@ table th .columntitle.name {
padding-right: 80px;
margin-left: 50px;
}
/* hover effect on sortable column */
table th a.columntitle:hover {
color: #000;
}
table th .sort-indicator {
width: 10px;
height: 8px;
margin-left: 10px;
margin-left: 5px;
display: inline-block;
vertical-align: text-bottom;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)";
filter: alpha(opacity=30);
opacity: .3;
}
.sort-indicator.hidden {
visibility: hidden;
}
table th:hover .sort-indicator.hidden {
visibility: visible;
}
table th, table td { border-bottom:1px solid #ddd; text-align:left; font-weight:normal; }
table td {
padding: 0 15px;
@@ -231,8 +239,8 @@ table td.filename a.name {
table tr[data-type="dir"] td.filename a.name span.nametext {font-weight:bold; }
table td.filename input.filename {
width: 70%;
margin-top: 1px;
margin-left: 48px;
margin-top: 6px;
margin-left: 7px;
cursor: text;
}
table td.filename a, table td.login, table td.logout, table td.download, table td.upload, table td.create, table td.delete { padding:3px 8px 8px 3px; }
@@ -345,14 +353,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 +374,6 @@ table td.filename .uploadtext {
left: 18px;
}
#fileList tr td.filename {
position: relative;
width: 100%;
@@ -432,7 +438,6 @@ a.action>img {
margin-bottom: -1px;
}
#fileList a.action {
display: inline;
padding: 18px 8px;
@@ -474,7 +479,7 @@ a.action>img {
.summary td {
padding-top: 20px;
padding-bottom: 250px;
padding-bottom: 150px;
border-bottom: none;
}
.summary .info {

View File

@@ -137,3 +137,27 @@
.oc-dialog .oc-dialog-buttonrow .cancel {
float:left;
}
.highlightUploaded {
-webkit-animation: highlightAnimation 2s 1;
-moz-animation: highlightAnimation 2s 1;
-o-animation: highlightAnimation 2s 1;
animation: highlightAnimation 2s 1;
}
@-webkit-keyframes highlightAnimation {
0% { background-color: rgba(255, 255, 140, 1); }
100% { background-color: rgba(0, 0, 0, 0); }
}
@-moz-keyframes highlightAnimation {
0% { background-color: rgba(255, 255, 140, 1); }
100% { background-color: rgba(0, 0, 0, 0); }
}
@-o-keyframes highlightAnimation {
0% { background-color: rgba(255, 255, 140, 1); }
100% { background-color: rgba(0, 0, 0, 0); }
}
@keyframes highlightAnimation {
0% { background-color: rgba(255, 255, 140, 1); }
100% { background-color: rgba(0, 0, 0, 0); }
}

View File

@@ -34,7 +34,7 @@ if(!\OC\Files\Filesystem::file_exists($filename)) {
exit;
}
$ftype=\OC\Files\Filesystem::getMimeType( $filename );
$ftype=\OC_Helper::getSecureMimeType(\OC\Files\Filesystem::getMimeType( $filename ));
header('Content-Type:'.$ftype);
OCP\Response::setContentDispositionHeader(basename($filename), 'attachment');

View File

@@ -24,6 +24,7 @@
initialize: function() {
this.navigation = new OCA.Files.Navigation($('#app-navigation'));
var urlParams = OC.Util.History.parseUrlQuery();
var fileActions = new OCA.Files.FileActions();
// default actions
fileActions.registerDefaultActions();
@@ -32,9 +33,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;
@@ -45,7 +48,8 @@
dragOptions: dragOptions,
folderDropOptions: folderDropOptions,
fileActions: fileActions,
allowLegacyActions: true
allowLegacyActions: true,
scrollTo: urlParams.scrollto
}
);
this.files.initialize();
@@ -56,7 +60,33 @@
this._setupEvents();
// trigger URL change event handlers
this._onPopState(OC.Util.History.parseUrlQuery());
this._onPopState(urlParams);
},
/**
* 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
);
}
},
/**

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())
});
}
@@ -424,6 +427,14 @@ OC.Upload = {
data.textStatus = 'servererror';
data.errorThrown = result[0].data.message; // error message has been translated on server
fu._trigger('fail', e, data);
} else { // Successful upload
// Checking that the uploaded file is the last one and contained in the current directory
if (data.files[0] === data.originalFiles[data.originalFiles.length - 1] &&
result[0].directory === FileList.getCurrentDirectory()) {
// Scroll to the last uploaded file and highlight all of them
var fileList = _.pluck(data.originalFiles, 'name');
FileList.highlightFiles(fileList);
}
}
},
/**
@@ -618,7 +629,7 @@ OC.Upload = {
},
function(result) {
if (result.status === 'success') {
FileList.add(result.data, {hidden: hidden, animate: true});
FileList.add(result.data, {hidden: hidden, animate: true, scrollTo: true});
} else {
OC.dialogs.alert(result.data.message, t('core', 'Could not create file'));
}
@@ -634,7 +645,7 @@ OC.Upload = {
},
function(result) {
if (result.status === 'success') {
FileList.add(result.data, {hidden: hidden, animate: true});
FileList.add(result.data, {hidden: hidden, animate: true, scrollTo: true});
} else {
OC.dialogs.alert(result.data.message, t('core', 'Could not create folder'));
}

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 + '/';
}

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'),
@@ -103,9 +103,10 @@
* @param $el container element with existing markup for the #controls
* and a table
* @param options map of options, see other parameters
* @param scrollContainer scrollable container, defaults to $(window)
* @param dragOptions drag options, disabled by default
* @param folderDropOptions folder drop options, disabled by default
* @param options.scrollContainer scrollable container, defaults to $(window)
* @param options.dragOptions drag options, disabled by default
* @param options.folderDropOptions folder drop options, disabled by default
* @param options.scrollTo name of file to scroll to after the first load
*/
initialize: function($el, options) {
var self = this;
@@ -165,6 +166,12 @@
this.setupUploadEvents();
this.$container.on('scroll', _.bind(this._onScroll, this));
if (options.scrollTo) {
this.$fileList.one('updated', function() {
self.scrollTo(options.scrollTo);
});
}
},
/**
@@ -172,7 +179,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 +190,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 +345,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 +377,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 +406,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 +611,10 @@
"data-permissions": fileData.permissions || this.getDirectoryPermissions()
});
if (fileData.mountType) {
tr.attr('data-mounttype', fileData.mountType);
}
if (!_.isUndefined(path)) {
tr.attr('data-path', path);
}
@@ -699,8 +716,10 @@
*
* @param fileData map of file attributes
* @param options map of attributes:
* - "updateSummary": true to update the summary after adding (default), false otherwise
* - "silent": true to prevent firing events like "fileActionsReady"
* @param options.updateSummary true to update the summary after adding (default), false otherwise
* @param options.silent true to prevent firing events like "fileActionsReady"
* @param options.animate true to animate preview loading (defaults to true here)
* @param options.scrollTo true to automatically scroll to the file's location
* @return new tr element (not appended to the table)
*/
add: function(fileData, options) {
@@ -708,7 +727,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
@@ -749,6 +768,10 @@
});
}
if (options.scrollTo) {
this.scrollTo(fileData.name);
}
// defaults to true if not defined
if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
this.fileSummary.add(fileData, true);
@@ -766,6 +789,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 +831,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 +932,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 +980,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 +1010,7 @@
}
this.setFiles(result.data.files);
return true
return true;
},
updateStorageStatistics: function(force) {
@@ -1291,6 +1333,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;
@@ -1473,16 +1519,15 @@
this.$table.removeClass('hidden');
},
scrollTo:function(file) {
//scroll to and highlight preselected file
var $scrollToRow = this.findFileEl(file);
if ($scrollToRow.exists()) {
$scrollToRow.addClass('searchresult');
$(window).scrollTop($scrollToRow.position().top);
//remove highlight when hovered over
$scrollToRow.one('hover', function() {
$scrollToRow.removeClass('searchresult');
});
if (!_.isArray(file)) {
file = [file];
}
this.highlightFiles(file, function($tr) {
$tr.addClass('searchresult');
$tr.one('hover', function() {
$tr.removeClass('searchresult');
});
});
},
filter:function(query) {
this.$fileList.find('tr').each(function(i,e) {
@@ -1566,7 +1611,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('('));
@@ -1581,6 +1626,18 @@
return name;
},
/**
* Shows a "permission denied" notification
*/
_showPermissionDeniedNotification: function() {
var message = t('core', 'You dont have permission to upload or create files here');
OC.Notification.show(message);
//hide notification after 10 sec
setTimeout(function() {
OC.Notification.hide();
}, 5000);
},
/**
* Setup file upload events related to the file-upload plugin
*/
@@ -1612,6 +1669,12 @@
// remember as context
data.context = dropTarget;
// if permissions are specified, only allow if create permission is there
var permissions = dropTarget.data('permissions');
if (!_.isUndefined(permissions) && (permissions & OC.PERMISSION_CREATE) === 0) {
self._showPermissionDeniedNotification();
return false;
}
var dir = dropTarget.data('file');
// if from file list, need to prepend parent dir
if (dir) {
@@ -1636,6 +1699,7 @@
// cancel uploads to current dir if no permission
var isCreatable = (self.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0;
if (!isCreatable) {
self._showPermissionDeniedNotification();
return false;
}
}
@@ -1806,6 +1870,68 @@
self.updateStorageStatistics();
});
},
/**
* Scroll to the last file of the given list
* Highlight the list of files
* @param files array of filenames,
* @param {Function} [highlightFunction] optional function
* to be called after the scrolling is finished
*/
highlightFiles: function(files, highlightFunction) {
// Detection of the uploaded element
var filename = files[files.length - 1];
var $fileRow = this.findFileEl(filename);
while(!$fileRow.exists() && this._nextPage(false) !== false) { // Checking element existence
$fileRow = this.findFileEl(filename);
}
if (!$fileRow.exists()) { // Element not present in the file list
return;
}
var currentOffset = this.$container.scrollTop();
var additionalOffset = this.$el.find("#controls").height()+this.$el.find("#controls").offset().top;
// Animation
var _this = this;
var $scrollContainer = this.$container;
if ($scrollContainer[0] === window) {
// need to use "body" to animate scrolling
// when the scroll container is the window
$scrollContainer = $('body');
}
$scrollContainer.animate({
// Scrolling to the top of the new element
scrollTop: currentOffset + $fileRow.offset().top - $fileRow.height() * 2 - additionalOffset
}, {
duration: 500,
complete: function() {
// Highlighting function
var highlightRow = highlightFunction;
if (!highlightRow) {
highlightRow = function($fileRow) {
$fileRow.addClass("highlightUploaded");
setTimeout(function() {
$fileRow.removeClass("highlightUploaded");
}, 2500);
};
}
// Loop over uploaded files
for(var i=0; i<files.length; i++) {
var $fileRow = _this.findFileEl(files[i]);
if($fileRow.length !== 0) { // Checking element existence
highlightRow($fileRow);
}
}
}
});
}
};

View File

@@ -433,7 +433,12 @@ var folderDropOptions = {
return false;
}
var targetPath = FileList.getCurrentDirectory() + '/' + $(this).closest('tr').data('file');
var $tr = $(this).closest('tr');
if (($tr.data('permissions') & OC.PERMISSION_CREATE) === 0) {
FileList._showPermissionDeniedNotification();
return false;
}
var targetPath = FileList.getCurrentDirectory() + '/' + $tr.data('file');
var files = FileList.getSelectedFiles();
if (files.length === 0) {

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;

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()) {
@@ -92,7 +93,7 @@ class Helper
public static function compareSize($a, $b) {
$aSize = $a->getSize();
$bSize = $b->getSize();
return $aSize - $bSize;
return ($aSize < $bSize) ? -1 : 1;
}
/**
@@ -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;
}

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']);
}

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();

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',

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);
});
});
/**
@@ -1832,7 +1835,6 @@ describe('OCA.Files.FileList tests', function() {
// but it makes it possible to simulate the event triggering to
// test the response of the handlers
$uploader = $('#file_upload_start');
fileList.setupUploadEvents();
fileList.setFiles(testFiles);
});
@@ -1909,6 +1911,16 @@ describe('OCA.Files.FileList tests', function() {
ev = dropOn(fileList.$fileList.find('th:first'));
expect(ev.result).toEqual(false);
expect(notificationStub.calledOnce).toEqual(true);
});
it('drop on an folder does not trigger upload if no upload permission on that folder', function() {
var $tr = fileList.findFileEl('somedir');
var ev;
$tr.data('permissions', OC.PERMISSION_READ);
ev = dropOn($tr);
expect(ev.result).toEqual(false);
expect(notificationStub.calledOnce).toEqual(true);
});
it('drop on a file row inside the table triggers upload to current folder', function() {
var ev;
@@ -1933,4 +1945,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);
});
});
});

View File

@@ -16,8 +16,28 @@ use OCA\Encryption;
$l = OC_L10N::get('files_encryption');
$return = false;
// Enable recoveryAdmin
$errorMessage = $l->t("Unknown error");
//check if both passwords are the same
if (empty($_POST['recoveryPassword'])) {
$errorMessage = $l->t('Missing recovery key password');
\OCP\JSON::error(array('data' => array('message' => $errorMessage)));
exit();
}
if (empty($_POST['confirmPassword'])) {
$errorMessage = $l->t('Please repeat the recovery key password');
\OCP\JSON::error(array('data' => array('message' => $errorMessage)));
exit();
}
if ($_POST['recoveryPassword'] !== $_POST['confirmPassword']) {
$errorMessage = $l->t('Repeated recovery key password does not match the provided recovery key password');
\OCP\JSON::error(array('data' => array('message' => $errorMessage)));
exit();
}
// Enable recoveryAdmin
$recoveryKeyId = \OC::$server->getAppConfig()->getValue('files_encryption', 'recoveryKeyId');
if (isset($_POST['adminEnableRecovery']) && $_POST['adminEnableRecovery'] === '1') {
@@ -26,14 +46,9 @@ if (isset($_POST['adminEnableRecovery']) && $_POST['adminEnableRecovery'] === '1
// Return success or failure
if ($return) {
\OCP\JSON::success(array('data' => array('message' => $l->t('Recovery key successfully enabled'))));
$successMessage = $l->t('Recovery key successfully enabled');
} else {
\OCP\JSON::error(array(
'data' => array(
'message' => $l->t(
'Could not enable recovery key. Please check your recovery key password!')
)
));
$errorMessage = $l->t('Could not disable recovery key. Please check your recovery key password!');
}
// Disable recoveryAdmin
@@ -43,17 +58,16 @@ if (isset($_POST['adminEnableRecovery']) && $_POST['adminEnableRecovery'] === '1
) {
$return = \OCA\Encryption\Helper::adminDisableRecovery($_POST['recoveryPassword']);
// Return success or failure
if ($return) {
\OCP\JSON::success(array('data' => array('message' => $l->t('Recovery key successfully disabled'))));
$successMessage = $l->t('Recovery key successfully disabled');
} else {
\OCP\JSON::error(array(
'data' => array(
'message' => $l->t(
'Could not disable recovery key. Please check your recovery key password!')
)
));
$errorMessage = $l->t('Could not disable recovery key. Please check your recovery key password!');
}
}
// Return success or failure
if ($return) {
\OCP\JSON::success(array('data' => array('message' => $successMessage)));
} else {
\OCP\JSON::error(array('data' => array('message' => $errorMessage)));
}

View File

@@ -21,6 +21,32 @@ $return = false;
$oldPassword = $_POST['oldPassword'];
$newPassword = $_POST['newPassword'];
$confirmPassword = $_POST['confirmPassword'];
//check if both passwords are the same
if (empty($_POST['oldPassword'])) {
$errorMessage = $l->t('Please provide the old recovery password');
\OCP\JSON::error(array('data' => array('message' => $errorMessage)));
exit();
}
if (empty($_POST['newPassword'])) {
$errorMessage = $l->t('Please provide a new recovery password');
\OCP\JSON::error(array('data' => array('message' => $errorMessage)));
exit();
}
if (empty($_POST['confirmPassword'])) {
$errorMessage = $l->t('Please repeat the new recovery password');
\OCP\JSON::error(array('data' => array('message' => $errorMessage)));
exit();
}
if ($_POST['newPassword'] !== $_POST['confirmPassword']) {
$errorMessage = $l->t('Repeated recovery key password does not match the provided recovery key password');
\OCP\JSON::error(array('data' => array('message' => $errorMessage)));
exit();
}
$view = new \OC\Files\View('/');
$util = new \OCA\Encryption\Util(new \OC\Files\View('/'), \OCP\User::getUser());
@@ -35,11 +61,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;

View File

@@ -18,6 +18,7 @@ use OCA\Encryption;
$l = OC_L10N::get('core');
$return = false;
$errorMessage = $l->t('Could not update the private key password.');
$oldPassword = $_POST['oldPassword'];
$newPassword = $_POST['newPassword'];
@@ -26,30 +27,43 @@ $view = new \OC\Files\View('/');
$session = new \OCA\Encryption\Session($view);
$user = \OCP\User::getUser();
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
// check new password
$passwordCorrect = \OCP\User::checkPassword($user, $newPassword);
$keyPath = '/' . $user . '/files_encryption/' . $user . '.private.key';
if ($passwordCorrect !== false) {
$encryptedKey = $view->file_get_contents($keyPath);
$decryptedKey = \OCA\Encryption\Crypt::decryptPrivateKey($encryptedKey, $oldPassword);
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
if ($decryptedKey) {
$keyPath = '/' . $user . '/files_encryption/' . $user . '.private.key';
$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($decryptedKey, $newPassword);
$view->file_put_contents($keyPath, $encryptedKey);
$encryptedKey = $view->file_get_contents($keyPath);
$decryptedKey = \OCA\Encryption\Crypt::decryptPrivateKey($encryptedKey, $oldPassword);
$session->setPrivateKey($decryptedKey);
if ($decryptedKey) {
$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;
}
} else {
$result = false;
$errorMessage = $l->t('The old password was not correct, please try again.');
}
$return = true;
\OC_FileProxy::$enabled = $proxyStatus;
} else {
$result = false;
$errorMessage = $l->t('The current log-in password was not correct, please try again.');
}
\OC_FileProxy::$enabled = $proxyStatus;
// success or failure
if ($return) {
$session->setInitialized(\OCA\Encryption\Session::INIT_SUCCESSFUL);
\OCP\JSON::success(array('data' => array('message' => $l->t('Private key password successfully updated.'))));
} else {
\OCP\JSON::error(array('data' => array('message' => $l->t('Could not update the private key password. Maybe the old password was not correct.'))));
\OCP\JSON::error(array('data' => array('message' => $errorMessage)));
}

View File

@@ -13,6 +13,8 @@ use OCA\Encryption;
\OCP\JSON::checkAppEnabled('files_encryption');
\OCP\JSON::callCheck();
$l = \OC::$server->getL10N('files_encryption');
if (
isset($_POST['userEnableRecovery'])
&& (0 == $_POST['userEnableRecovery'] || '1' === $_POST['userEnableRecovery'])
@@ -38,4 +40,8 @@ if (
}
// Return success or failure
($return) ? \OCP\JSON::success() : \OCP\JSON::error();
if ($return) {
\OCP\JSON::success(array('data' => array('message' => $l->t('File recovery settings updated'))));
} else {
\OCP\JSON::error(array('data' => array('message' => $l->t('Could not update file recovery'))));
}

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');
@@ -31,22 +35,6 @@ if (!OC_Config::getValue('maintenance', false)) {
if(!in_array('crypt', stream_get_wrappers())) {
stream_wrapper_register('crypt', 'OCA\Encryption\Stream');
}
// check if we are logged in
if (OCP\User::isLoggedIn()) {
// ensure filesystem is loaded
if (!\OC\Files\Filesystem::$loaded) {
\OC_Util::setupFS();
}
$view = new OC\Files\View('/');
$sessionReady = OCA\Encryption\Helper::checkRequirements();
if($sessionReady) {
$session = new \OCA\Encryption\Session($view);
}
}
} else {
// logout user if we are in maintenance to force re-login
OCP\User::logout();

View File

@@ -2,17 +2,22 @@
<info>
<id>files_encryption</id>
<name>Encryption</name>
<description>The ownCloud files encryption system provides server side-encryption. After the app is enabled you need to re-login to initialize your encryption keys. Please note that server side encryption requires that the ownCloud server admin can be trusted. The main purpose of this app is the encryption of files that are stored on externally mounted storages.</description>
<description>
This application encrypts all files accessed by ownCloud at rest, wherever they are stored. As an example, with this application enabled, external cloud based Amazon S3 storage will be encrypted, protecting this data on storage outside of the control of the Admin. When this application is enabled for the first time, all files are encrypted as users log in and are prompted for their password. The recommended recovery key option enables recovery of files in case the key is lost.
Note that this app encrypts all files that are touched by ownCloud, so external storage providers and applications such as SharePoint will see new files encrypted when they are accessed. Encryption is based on AES 128 or 256 bit keys. More information is available in the Encryption documentation.
</description>
<licence>AGPL</licence>
<author>Sam Tuke, Bjoern Schiessle, Florin Peter</author>
<requiremin>4</requiremin>
<shipped>true</shipped>
<documentation>
<user>http://doc.owncloud.org/server/6.0/user_manual/files/encryption.html</user>
<admin>http://doc.owncloud.org/server/6.0/admin_manual/configuration/configuration_encryption.html</admin>
<user>user-encryption</user>
<admin>admin-encryption</admin>
</documentation>
<rememberlogin>false</rememberlogin>
<types>
<filesystem/>
</types>
<ocsid>166047</ocsid>
</info>

View File

@@ -1 +1 @@
0.6
0.6.1

View File

@@ -2,9 +2,13 @@
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');
OC_JSON::checkAppEnabled('files_encryption');
OC_App::loadApp('files_encryption');
if (isset($_GET['errorCode'])) {
$errorCode = $_GET['errorCode'];
switch ($errorCode) {

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;
}
@@ -198,19 +205,22 @@ class Hooks {
if (Crypt::mode() === 'server') {
$view = new \OC\Files\View('/');
$session = new \OCA\Encryption\Session($view);
if ($params['uid'] === \OCP\User::getUser()) {
// Get existing decrypted private key
$privateKey = $session->getPrivateKey();
$session = new \OCA\Encryption\Session($view);
// Get existing decrypted private key
$privateKey = $session->getPrivateKey();
if ($params['uid'] === \OCP\User::getUser() && $privateKey) {
// 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
@@ -231,6 +241,9 @@ class Hooks {
|| !$util->userKeysExists()
|| !$view->file_exists($user . '/files')) {
// backup old keys
$util->backupAllKeys('recovery');
$newUserPassword = $params['password'];
// make sure that the users home is mounted
@@ -245,16 +258,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 +317,7 @@ class Hooks {
}
/**
* @brief
* update share keys if a file was shared
*/
public static function postShared($params) {
@@ -313,34 +327,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 +409,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);
}
}
@@ -397,6 +423,18 @@ class Hooks {
* @param array $params with the old path and the new path
*/
public static function preRename($params) {
self::preRenameOrCopy($params, 'rename');
}
/**
* mark file as copied so that we know the original source after the file was copied
* @param array $params with the old path and the new path
*/
public static function preCopy($params) {
self::preRenameOrCopy($params, 'copy');
}
private static function preRenameOrCopy($params, $operation) {
$user = \OCP\User::getUser();
$view = new \OC\Files\View('/');
$util = new Util($view, $user);
@@ -407,21 +445,39 @@ 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) {
if ($util->isSystemWideMountPoint($pathOld)) {
$oldShareKeyPath = 'files_encryption/share-keys/' . $pathOld;
} else {
$oldShareKeyPath = $ownerOld . '/' . 'files_encryption/share-keys/' . $pathOld;
}
// gather share keys here because in postRename() the file will be moved already
$oldShareKeys = Helper::findShareKeys($pathOld, $oldShareKeyPath, $view);
if (count($oldShareKeys) === 0) {
\OC_Log::write(
'Encryption library', 'No share keys found for "' . $pathOld . '"',
\OC_Log::WARN
);
}
self::$renamedFiles[$params['oldpath']] = array(
'uid' => $ownerOld,
'path' => $pathOld);
'path' => $pathOld,
'type' => $type,
'operation' => $operation,
'sharekeys' => $oldShareKeys
);
}
}
/**
* 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
* after a file is renamed/copied, rename/copy its keyfile and share-keys also fix the file size and fix also the sharing
*
* 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
* @param array $params array with oldpath and newpath
*/
public static function postRename($params) {
public static function postRenameOrCopy($params) {
if (\OCP\App::isEnabled('files_encryption') === false) {
return true;
@@ -432,16 +488,21 @@ 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);
$oldShareKeys = null;
if (isset(self::$renamedFiles[$params['oldpath']]['uid']) &&
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'];
$oldShareKeys = self::$renamedFiles[$params['oldpath']]['sharekeys'];
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);
\OC_FileProxy::$enabled = $proxyStatus;
return false;
}
@@ -473,35 +534,28 @@ class Hooks {
}
// handle share keys
if (!$view->is_dir($oldKeyfilePath)) {
if ($type === 'file') {
$oldKeyfilePath .= '.key';
$newKeyfilePath .= '.key';
// handle share-keys
$matches = Helper::findShareKeys($oldShareKeyPath, $view);
foreach ($matches as $src) {
foreach ($oldShareKeys 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 +649,7 @@ class Hooks {
}
/**
* unmount file from yourself
* remember files/folders which get unmounted
*/
public static function preUmount($params) {
@@ -613,6 +668,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 +700,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);
}
}
}

View File

@@ -9,32 +9,21 @@
$(document).ready(function(){
$('input:password[name="encryptionRecoveryPassword"]').keyup(function(event) {
var recoveryPassword = $( '#encryptionRecoveryPassword' ).val();
var recoveryPasswordRepeated = $( '#repeatEncryptionRecoveryPassword' ).val();
var checkedButton = $('input:radio[name="adminEnableRecovery"]:checked').val();
var uncheckedValue = (1+parseInt(checkedButton)) % 2;
if (recoveryPassword !== '' && recoveryPassword === recoveryPasswordRepeated) {
$('input:radio[name="adminEnableRecovery"][value="'+uncheckedValue.toString()+'"]').removeAttr("disabled");
} else {
$('input:radio[name="adminEnableRecovery"][value="'+uncheckedValue.toString()+'"]').attr("disabled", "true");
}
});
$( 'input:radio[name="adminEnableRecovery"]' ).change(
function() {
var recoveryStatus = $( this ).val();
var oldStatus = (1+parseInt(recoveryStatus)) % 2;
var recoveryPassword = $( '#encryptionRecoveryPassword' ).val();
var confirmPassword = $( '#repeatEncryptionRecoveryPassword' ).val();
OC.msg.startSaving('#encryptionSetRecoveryKey .msg');
$.post(
OC.filePath( 'files_encryption', 'ajax', 'adminrecovery.php' )
, { adminEnableRecovery: recoveryStatus, recoveryPassword: recoveryPassword }
, { adminEnableRecovery: recoveryStatus, recoveryPassword: recoveryPassword, confirmPassword: confirmPassword }
, function( result ) {
OC.msg.finishedSaving('#encryptionSetRecoveryKey .msg', result);
if (result.status === "error") {
OC.Notification.show(t('admin', result.data.message));
$('input:radio[name="adminEnableRecovery"][value="'+oldStatus.toString()+'"]').attr("checked", "true");
} else {
OC.Notification.hide();
if (recoveryStatus === "0") {
$('p[name="changeRecoveryPasswordBlock"]').addClass("hidden");
} else {
@@ -49,33 +38,17 @@ $(document).ready(function(){
// change recovery password
$('input:password[name="changeRecoveryPassword"]').keyup(function(event) {
var oldRecoveryPassword = $('#oldEncryptionRecoveryPassword').val();
var newRecoveryPassword = $('#newEncryptionRecoveryPassword').val();
var newRecoveryPasswordRepeated = $('#repeatedNewEncryptionRecoveryPassword').val();
if (newRecoveryPassword !== '' && oldRecoveryPassword !== '' && newRecoveryPassword === newRecoveryPasswordRepeated) {
$('button:button[name="submitChangeRecoveryKey"]').removeAttr("disabled");
} else {
$('button:button[name="submitChangeRecoveryKey"]').attr("disabled", "true");
}
});
$('button:button[name="submitChangeRecoveryKey"]').click(function() {
var oldRecoveryPassword = $('#oldEncryptionRecoveryPassword').val();
var newRecoveryPassword = $('#newEncryptionRecoveryPassword').val();
OC.msg.startSaving('#encryption .msg');
var confirmNewPassword = $('#repeatedNewEncryptionRecoveryPassword').val();
OC.msg.startSaving('#encryptionChangeRecoveryKey .msg');
$.post(
OC.filePath( 'files_encryption', 'ajax', 'changeRecoveryPassword.php' )
, { oldPassword: oldRecoveryPassword, newPassword: newRecoveryPassword }
, { oldPassword: oldRecoveryPassword, newPassword: newRecoveryPassword, confirmPassword: confirmNewPassword }
, function( data ) {
if (data.status == "error") {
OC.msg.finishedSaving('#encryption .msg', data);
} else {
OC.msg.finishedSaving('#encryption .msg', data);
OC.msg.finishedSaving('#encryptionChangeRecoveryKey .msg', data);
}
}
);
});

View File

@@ -26,36 +26,27 @@ $(document).ready(function(){
// Trigger ajax on recoveryAdmin status change
$( 'input:radio[name="userEnableRecovery"]' ).change(
function() {
// Hide feedback messages in case they're already visible
$('#recoveryEnabledSuccess').hide();
$('#recoveryEnabledError').hide();
var recoveryStatus = $( this ).val();
OC.msg.startAction('#userEnableRecovery .msg', 'Updating recovery keys. This can take some time...');
$.post(
OC.filePath( 'files_encryption', 'ajax', 'userrecovery.php' )
, { userEnableRecovery: recoveryStatus }
, function( data ) {
if ( data.status == "success" ) {
$('#recoveryEnabledSuccess').show();
} else {
$('#recoveryEnabledError').show();
}
OC.msg.finishedAction('#userEnableRecovery .msg', data);
}
);
// Ensure page is not reloaded on form submit
return false;
}
);
$("#encryptAll").click(
function(){
// Hide feedback messages in case they're already visible
$('#encryptAllSuccess').hide();
$('#encryptAllError').hide();
var userPassword = $( '#userPassword' ).val();
var encryptAll = $( '#encryptAll' ).val();
@@ -73,7 +64,7 @@ $(document).ready(function(){
// Ensure page is not reloaded on form submit
return false;
}
);
// update private key password

155
apps/files_encryption/lib/crypt.php Executable file → Normal file
View File

@@ -24,6 +24,7 @@
*/
namespace OCA\Encryption;
use OCA\Encryption\Exceptions\EncryptionException;
require_once __DIR__ . '/../3rdparty/Crypt_Blowfish/Blowfish.php';
@@ -38,6 +39,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 +219,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 +242,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 +301,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 +315,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 (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 +335,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 +345,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 +359,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 +371,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 +380,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 +410,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 +418,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 +445,7 @@ class Crypt {
);
} else {
return false;
throw new Exceptions\MultiKeyEncryptException('multi key encryption failed: ' . openssl_error_string(), 20);
}
}
@@ -438,8 +455,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 +465,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 +473,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 +591,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;
}
}

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 {
}

88
apps/files_encryption/lib/helper.php Executable file → Normal file
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
@@ -431,24 +431,40 @@ class Helper {
/**
* find all share keys for a given file
* @param string $path to the file
* @param \OC\Files\View $view view, relative to data/
* @return array list of files, path relative to data/
*
* @param string $filePath path to the file name relative to the user's files dir
* for example "subdir/filename.txt"
* @param string $shareKeyPath share key prefix path relative to the user's data dir
* for example "user1/files_encryption/share-keys/subdir/filename.txt"
* @param \OC\Files\View $rootView root view, relative to data/
* @return array list of share key files, path relative to data/$user
*/
public static function findShareKeys($path, $view) {
public static function findShareKeys($filePath, $shareKeyPath, $rootView) {
$result = array();
$pathinfo = pathinfo($path);
$dirContent = $view->opendir($pathinfo['dirname']);
if (is_resource($dirContent)) {
while (($file = readdir($dirContent)) !== false) {
if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
if (preg_match("/" . $pathinfo['filename'] . ".(.*).shareKey/", $file)) {
$result[] = $pathinfo['dirname'] . '/' . $file;
}
}
$user = \OCP\User::getUser();
$util = new Util($rootView, $user);
// get current sharing state
$sharingEnabled = \OCP\Share::isEnabled();
// get users sharing this file
$usersSharing = $util->getSharingUsersArray($sharingEnabled, $filePath);
$pathinfo = pathinfo($shareKeyPath);
$baseDir = $pathinfo['dirname'] . '/';
$fileName = $pathinfo['basename'];
foreach ($usersSharing as $user) {
$keyName = $fileName . '.' . $user . '.shareKey';
if ($rootView->file_exists($baseDir . $keyName)) {
$result[] = $baseDir . $keyName;
} else {
\OC_Log::write(
'Encryption library',
'No share key found for user "' . $user . '" for file "' . $fileName . '"',
\OC_Log::WARN
);
}
closedir($dirContent);
}
return $result;
@@ -475,5 +491,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;
}
}

104
apps/files_encryption/lib/keymanager.php Executable file → Normal file
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
*
@@ -428,13 +459,17 @@ class Keymanager {
\OCP\Util::writeLog('files_encryption', 'delAllShareKeys: delete share keys: ' . $baseDir . $filePath, \OCP\Util::DEBUG);
$result = $view->unlink($baseDir . $filePath);
} else {
$parentDir = dirname($baseDir . $filePath);
$filename = pathinfo($filePath, PATHINFO_BASENAME);
foreach($view->getDirectoryContent($parentDir) as $content) {
$path = $content['path'];
if (self::getFilenameFromShareKey($content['name']) === $filename) {
\OCP\Util::writeLog('files_encryption', 'dellAllShareKeys: delete share keys: ' . '/' . $userId . '/' . $path, \OCP\Util::DEBUG);
$result &= $view->unlink('/' . $userId . '/' . $path);
$sharingEnabled = \OCP\Share::isEnabled();
$users = $util->getSharingUsersArray($sharingEnabled, $filePath);
foreach($users as $user) {
$keyName = $baseDir . $filePath . '.' . $user . '.shareKey';
if ($view->file_exists($keyName)) {
\OCP\Util::writeLog(
'files_encryption',
'dellAllShareKeys: delete share keys: "' . $keyName . '"',
\OCP\Util::DEBUG
);
$result &= $view->unlink($keyName);
}
}
}
@@ -444,17 +479,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);
@@ -507,17 +543,20 @@ class Keymanager {
if ($view->is_dir($dir . '/' . $file)) {
self::recursiveDelShareKeys($dir . '/' . $file, $userIds, $owner, $view);
} else {
$realFile = $realFileDir . self::getFilenameFromShareKey($file);
foreach ($userIds as $userId) {
if (preg_match("/(.*)." . $userId . ".shareKey/", $file)) {
if ($userId === $owner &&
$view->file_exists($realFile)) {
\OCP\Util::writeLog('files_encryption', 'original file still exists, keep owners share key!', \OCP\Util::ERROR);
continue;
}
\OCP\Util::writeLog('files_encryption', 'recursiveDelShareKey: delete share key: ' . $file, \OCP\Util::DEBUG);
$view->unlink($dir . '/' . $file);
$fileNameFromShareKey = self::getFilenameFromShareKey($file, $userId);
if (!$fileNameFromShareKey) {
continue;
}
$realFile = $realFileDir . $fileNameFromShareKey;
if ($userId === $owner &&
$view->file_exists($realFile)) {
\OCP\Util::writeLog('files_encryption', 'original file still exists, keep owners share key!', \OCP\Util::ERROR);
continue;
}
\OCP\Util::writeLog('files_encryption', 'recursiveDelShareKey: delete share key: ' . $file, \OCP\Util::DEBUG);
$view->unlink($dir . '/' . $file);
}
}
}
@@ -559,16 +598,19 @@ class Keymanager {
/**
* extract filename from share key name
* @param string $shareKey (filename.userid.sharekey)
* @param string $userId
* @return string|false filename or false
*/
protected static function getFilenameFromShareKey($shareKey) {
$parts = explode('.', $shareKey);
protected static function getFilenameFromShareKey($shareKey, $userId) {
$expectedSuffix = '.' . $userId . '.' . 'shareKey';
$suffixLen = strlen($expectedSuffix);
$filename = false;
if(count($parts) > 2) {
$filename = implode('.', array_slice($parts, 0, count($parts)-2));
$suffix = substr($shareKey, -$suffixLen);
if ($suffix !== $expectedSuffix) {
return false;
}
return $filename;
return substr($shareKey, 0, -$suffixLen);
}
}

View File

@@ -49,12 +49,17 @@ class Proxy extends \OC_FileProxy {
* @param string $uid user
* @return boolean
*/
private function isExcludedPath($path, $uid) {
protected function isExcludedPath($path, $uid) {
$view = new \OC\Files\View();
// files outside of the files-folder are excluded
if(strpos($path, '/' . $uid . '/files') !== 0) {
$path = \OC\Files\Filesystem::normalizePath($path);
// we only encrypt/decrypt files in the files and files_versions folder
if(
strpos($path, '/' . $uid . '/files/') !== 0 &&
strpos($path, '/' . $uid . '/files_versions/') !== 0) {
return true;
}
@@ -157,8 +162,8 @@ class Proxy extends \OC_FileProxy {
// store new unenecrypted size so that it can be updated
// in the post proxy
$tmpFileInfo = $view->getFileInfo($tmpPath);
if ( isset($tmpFileInfo['size']) ) {
self::$unencryptedSizes[\OC\Files\Filesystem::normalizePath($path)] = $tmpFileInfo['size'];
if ( isset($tmpFileInfo['unencrypted_size']) ) {
self::$unencryptedSizes[\OC\Files\Filesystem::normalizePath($path)] = $tmpFileInfo['unencrypted_size'];
}
// remove our temp file
@@ -262,7 +267,7 @@ class Proxy extends \OC_FileProxy {
* @param resource $result
* @return resource
*/
public function postFopen($path, &$result) {
public function postFopen($path, $result) {
$path = \OC\Files\Filesystem::normalizePath($path);

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

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

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']));
}
}
}
@@ -1487,6 +1525,22 @@ class Util {
$this->recoverAllFiles('/', $privateKey);
}
/**
* create a backup of all keys from the user
*
* @param string $purpose (optional) define the purpose of the backup, will be part of the backup folder
*/
public function backupAllKeys($purpose = '') {
$this->userId;
$backupDir = $this->encryptionDir . '/backup.';
$backupDir .= ($purpose === '') ? date("Y-m-d_H-i-s") . '/' : $purpose . '.' . date("Y-m-d_H-i-s") . '/';
$this->view->mkdir($backupDir);
$this->view->copy($this->shareKeysPath, $backupDir . 'share-keys/');
$this->view->copy($this->keyfilesPath, $backupDir . 'keyfiles/');
$this->view->copy($this->privateKeyPath, $backupDir . $this->userId . '.private.key');
$this->view->copy($this->publicKeyPath, $backupDir . $this->userId . '.public.key');
}
/**
* check if the file is stored on a system wide mount point
* @param string $path relative to /data/user with leading '/'
@@ -1495,16 +1549,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'

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');

View File

@@ -1,8 +1,12 @@
<form id="encryption" class="section">
<h2><?php p($l->t('Encryption')); ?></h2>
<p>
<?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 id="encryptionSetRecoveryKey">
<?php p($l->t("Enable recovery key (allow to recover users files in case of password loss):")); ?>
<span class="msg"></span>
<br/>
<br/>
<input type="password" name="encryptionRecoveryPassword" id="encryptionRecoveryPassword"/>
@@ -15,7 +19,7 @@
type='radio'
name='adminEnableRecovery'
value='1'
<?php echo($_["recoveryEnabled"] === '1' ? 'checked="checked"' : 'disabled'); ?> />
<?php echo($_["recoveryEnabled"] === '1' ? 'checked="checked"' : ''); ?> />
<?php p($l->t("Enabled")); ?>
<br/>
@@ -23,13 +27,14 @@
type='radio'
name='adminEnableRecovery'
value='0'
<?php echo($_["recoveryEnabled"] === '0' ? 'checked="checked"' : 'disabled'); ?> />
<?php echo($_["recoveryEnabled"] === '0' ? 'checked="checked"' : ''); ?> />
<?php p($l->t("Disabled")); ?>
</p>
<br/><br/>
<p name="changeRecoveryPasswordBlock" <?php if ($_['recoveryEnabled'] === '0') print_unescaped('class="hidden"');?>>
<p name="changeRecoveryPasswordBlock" id="encryptionChangeRecoveryKey" <?php if ($_['recoveryEnabled'] === '0') print_unescaped('class="hidden"');?>>
<strong><?php p($l->t("Change recovery key password:")); ?></strong>
<span class="msg"></span>
<br/><br/>
<input
type="password"
@@ -52,9 +57,9 @@
<br/>
<button
type="button"
name="submitChangeRecoveryKey"
disabled><?php p($l->t("Change Password")); ?>
name="submitChangeRecoveryKey">
<?php p($l->t("Change Password")); ?>
</button>
<span class="msg"></span>
</p>
<?php endif; ?>
</form>

View File

@@ -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,12 +36,12 @@
</button>
<span class="msg"></span>
</p>
<?php endif; ?>
<?php if ( $_["recoveryEnabled"] && $_["privateKeySet"] ): ?>
<?php elseif ( $_["recoveryEnabled"] && $_["privateKeySet"] && $_["initialized"] === \OCA\Encryption\Session::INIT_SUCCESSFUL ): ?>
<br />
<p>
<p id="userEnableRecovery">
<label for="userEnableRecovery"><?php p( $l->t( "Enable password recovery:" ) ); ?></label>
<span class="msg"></span>
<br />
<em><?php p( $l->t( "Enabling this option will allow you to reobtain access to your encrypted files in case of password loss" ) ); ?></em>
<br />
@@ -56,8 +59,6 @@
value='0'
<?php echo ( $_["recoveryEnabledForUser"] === false ? 'checked="checked"' : '' ); ?> />
<?php p( $l->t( "Disabled" ) ); ?>
<div id="recoveryEnabledSuccess"><?php p( $l->t( 'File recovery settings updated' ) ); ?></div>
<div id="recoveryEnabledError"><?php p( $l->t( 'Could not update file recovery' ) ); ?></div>
</p>
<?php endif; ?>
</form>

View File

@@ -95,11 +95,22 @@ 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() {
// cleanup test user
\OC_User::deleteUser(\Test_Encryption_Crypt::TEST_ENCRYPTION_CRYPT_USER1);
\OC_Hook::clear();
\OC_FileProxy::clearProxies();
// Delete keys in /data/
$view = new \OC\Files\View('/');
$view->rmdir('public-keys');
$view->rmdir('owncloud_private_key');
}
/**
@@ -120,7 +131,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 +163,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 +188,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 +206,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 +271,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 +291,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 +305,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 +327,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 +412,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));

View File

@@ -20,18 +20,27 @@ class Test_Encryption_Helper extends \PHPUnit_Framework_TestCase {
const TEST_ENCRYPTION_HELPER_USER1 = "test-helper-user1";
const TEST_ENCRYPTION_HELPER_USER2 = "test-helper-user2";
public static function setUpBeforeClass() {
public function setUp() {
// create test user
\Test_Encryption_Util::loginHelper(\Test_Encryption_Helper::TEST_ENCRYPTION_HELPER_USER2, true);
\Test_Encryption_Util::loginHelper(\Test_Encryption_Helper::TEST_ENCRYPTION_HELPER_USER1, true);
}
public static function tearDownAfterClass() {
public function tearDown() {
// cleanup test user
\OC_User::deleteUser(\Test_Encryption_Helper::TEST_ENCRYPTION_HELPER_USER1);
\OC_User::deleteUser(\Test_Encryption_Helper::TEST_ENCRYPTION_HELPER_USER2);
}
public static function tearDownAfterClass() {
\OC_Hook::clear();
\OC_FileProxy::clearProxies();
// Delete keys in /data/
$view = new \OC\Files\View('/');
$view->rmdir('public-keys');
$view->rmdir('owncloud_private_key');
}
/**
@@ -108,4 +117,55 @@ class Test_Encryption_Helper extends \PHPUnit_Framework_TestCase {
\Test_Encryption_Util::loginHelper(\Test_Encryption_Helper::TEST_ENCRYPTION_HELPER_USER1);
}
function userNamesProvider() {
return array(
array('testuser' . uniqid()),
array('user.name.with.dots'),
);
}
/**
* Tests whether share keys can be found
*
* @dataProvider userNamesProvider
*/
function testFindShareKeys($userName) {
// note: not using dataProvider as we want to make
// sure that the correct keys are match and not any
// other ones that might happen to have similar names
\Test_Encryption_Util::setupHooks();
\Test_Encryption_Util::loginHelper($userName, true);
$testDir = 'testFindShareKeys' . uniqid() . '/';
$baseDir = $userName . '/files/' . $testDir;
$fileList = array(
't est.txt',
't est_.txt',
't est.doc.txt',
't est(.*).txt', // make sure the regexp is escaped
'multiple.dots.can.happen.too.txt',
't est.' . $userName . '.txt',
't est_.' . $userName . '.shareKey.txt',
'who would upload their.shareKey',
'user ones file.txt',
'user ones file.txt.backup',
'.t est.txt'
);
$rootView = new \OC\Files\View('/');
$rootView->mkdir($baseDir);
foreach ($fileList as $fileName) {
$rootView->file_put_contents($baseDir . $fileName, 'dummy');
}
$shareKeysDir = $userName . '/files_encryption/share-keys/' . $testDir;
foreach ($fileList as $fileName) {
// make sure that every file only gets its correct respective keys
$result = Encryption\Helper::findShareKeys($baseDir . $fileName, $shareKeysDir . $fileName, $rootView);
$this->assertEquals(
array($shareKeysDir . $fileName . '.' . $userName . '.shareKey'),
$result
);
}
}
}

View File

@@ -36,8 +36,8 @@ use OCA\Encryption;
*/
class Test_Encryption_Hooks extends \PHPUnit_Framework_TestCase {
const TEST_ENCRYPTION_HOOKS_USER1 = "test-encryption-hooks-user1";
const TEST_ENCRYPTION_HOOKS_USER2 = "test-encryption-hooks-user2";
const TEST_ENCRYPTION_HOOKS_USER1 = "test-encryption-hooks-user1.dot";
const TEST_ENCRYPTION_HOOKS_USER2 = "test-encryption-hooks-user2.dot";
/**
* @var \OC\Files\View
@@ -49,7 +49,26 @@ class Test_Encryption_Hooks extends \PHPUnit_Framework_TestCase {
public $filename;
public $folder;
private static $testFiles;
public static function setUpBeforeClass() {
// note: not using a data provider because these
// files all need to coexist to make sure the
// share keys are found properly (pattern matching)
self::$testFiles = array(
't est.txt',
't est_.txt',
't est.doc.txt',
't est(.*).txt', // make sure the regexp is escaped
'multiple.dots.can.happen.too.txt',
't est.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.txt',
't est_.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey.txt',
'who would upload their.shareKey',
'user ones file.txt',
'user ones file.txt.backup',
'.t est.txt'
);
// reset backend
\OC_User::clearBackends();
\OC_User::useBackend('database');
@@ -98,6 +117,14 @@ class Test_Encryption_Hooks extends \PHPUnit_Framework_TestCase {
// cleanup test user
\OC_User::deleteUser(\Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER1);
\OC_User::deleteUser(\Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER2);
\OC_Hook::clear();
\OC_FileProxy::clearProxies();
// Delete keys in /data/
$view = new \OC\Files\View('/');
$view->rmdir('public-keys');
$view->rmdir('owncloud_private_key');
}
function testDisableHook() {
@@ -281,25 +308,33 @@ class Test_Encryption_Hooks extends \PHPUnit_Framework_TestCase {
}
}
function testRenameHook() {
// create all files to make sure all keys can coexist properly
foreach (self::$testFiles as $file) {
// save file with content
$cryptedFile = file_put_contents('crypt:///' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $file, $this->data);
// test that data was successfully written
$this->assertTrue(is_int($cryptedFile));
}
foreach (self::$testFiles as $file) {
$this->doTestRenameHook($file);
}
}
/**
* test rename operation
*/
function testRenameHook() {
// 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));
function doTestRenameHook($filename) {
// 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'));
. $filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
$this->assertTrue($this->rootView->file_exists(
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/'
. $this->filename . '.key'));
. $filename . '.key'));
// make subfolder and sub-subfolder
$this->rootView->mkdir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder);
@@ -310,31 +345,91 @@ class Test_Encryption_Hooks extends \PHPUnit_Framework_TestCase {
// move the file to the sub-subfolder
$root = $this->rootView->getRoot();
$this->rootView->chroot('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/');
$this->rootView->rename($this->filename, '/' . $this->folder . '/' . $this->folder . '/' . $this->filename);
$this->rootView->rename($filename, '/' . $this->folder . '/' . $this->folder . '/' . $filename);
$this->rootView->chroot($root);
$this->assertFalse($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));
$this->assertFalse($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $filename));
$this->assertTrue($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder . '/' . $filename));
// keys should be renamed too
$this->assertFalse($this->rootView->file_exists(
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
. $this->filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
. $filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
$this->assertFalse($this->rootView->file_exists(
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/'
. $this->filename . '.key'));
. $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'));
. $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'));
. $filename . '.key'));
// cleanup
$this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder);
}
function testCopyHook() {
// create all files to make sure all keys can coexist properly
foreach (self::$testFiles as $file) {
// save file with content
$cryptedFile = file_put_contents('crypt:///' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $file, $this->data);
// test that data was successfully written
$this->assertTrue(is_int($cryptedFile));
}
foreach (self::$testFiles as $file) {
$this->doTestCopyHook($file);
}
}
/**
* test rename operation
*/
function doTestCopyHook($filename) {
// check if keys exists
$this->assertTrue($this->rootView->file_exists(
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
. $filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
$this->assertTrue($this->rootView->file_exists(
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/'
. $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($filename, '/' . $this->folder . '/' . $this->folder . '/' . $filename);
$this->assertTrue($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $filename));
$this->assertTrue($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder . '/' . $filename));
// keys should be copied too
$this->assertTrue($this->rootView->file_exists(
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
. $filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
$this->assertTrue($this->rootView->file_exists(
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/'
. $filename . '.key'));
$this->assertTrue($this->rootView->file_exists(
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/' . $this->folder . '/' . $this->folder . '/'
. $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 . '/'
. $filename . '.key'));
// cleanup
$this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder);
$this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $filename);
}
/**
* @brief replacing encryption keys during password change should be allowed
* until the user logged in for the first time

View File

@@ -23,7 +23,7 @@ use OCA\Encryption;
*/
class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
const TEST_USER = "test-keymanager-user";
const TEST_USER = "test-keymanager-user.dot";
public $userId;
public $pass;
@@ -98,6 +98,14 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
if (self::$stateFilesTrashbin) {
OC_App::enable('files_trashbin');
}
\OC_Hook::clear();
\OC_FileProxy::clearProxies();
// Delete keys in /data/
$view = new \OC\Files\View('/');
$view->rmdir('public-keys');
$view->rmdir('owncloud_private_key');
}
/**
@@ -107,7 +115,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);
@@ -135,15 +143,25 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
$this->assertArrayHasKey('key', $sslInfo);
}
function fileNameFromShareKeyProvider() {
return array(
array('file.user.shareKey', 'user', 'file'),
array('file.name.with.dots.user.shareKey', 'user', 'file.name.with.dots'),
array('file.name.user.with.dots.shareKey', 'user.with.dots', 'file.name'),
array('file.txt', 'user', false),
array('user.shareKey', 'user', false),
);
}
/**
* @small
*
* @dataProvider fileNameFromShareKeyProvider
*/
function testGetFilenameFromShareKey() {
$this->assertEquals("file",
\TestProtectedKeymanagerMethods::testGetFilenameFromShareKey("file.user.shareKey"));
$this->assertEquals("file.name.with.dots",
\TestProtectedKeymanagerMethods::testGetFilenameFromShareKey("file.name.with.dots.user.shareKey"));
$this->assertFalse(\TestProtectedKeymanagerMethods::testGetFilenameFromShareKey("file.txt"));
function testGetFilenameFromShareKey($fileName, $user, $expectedFileName) {
$this->assertEquals($expectedFileName,
\TestProtectedKeymanagerMethods::testGetFilenameFromShareKey($fileName, $user)
);
}
/**
@@ -174,6 +192,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 +239,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);
@@ -217,6 +267,12 @@ 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.user1.shareKey', 'data');
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey', 'data');
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.user1.shareKey', 'data');
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.user1.test.shareKey', 'data');
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.test-keymanager-userxdot.shareKey', 'data');
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.userx.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey', 'data');
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.' . Test_Encryption_Keymanager::TEST_USER . '.userx.shareKey', 'data');
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.user1.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey', 'data');
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.' . Test_Encryption_Keymanager::TEST_USER . '.user1.shareKey', 'data');
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file2.user2.shareKey', 'data');
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file2.user3.shareKey', 'data');
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/subfolder/file2.user3.shareKey', 'data');
@@ -225,7 +281,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(
@@ -247,6 +303,23 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
$this->assertTrue($this->view->file_exists(
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/subfolder/file2.user3.shareKey'));
// check if share keys for user or file with similar name
$this->assertTrue($this->view->file_exists(
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.user1.test.shareKey'));
$this->assertTrue($this->view->file_exists(
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.test-keymanager-userxdot.shareKey'));
$this->assertTrue($this->view->file_exists(
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.' . Test_Encryption_Keymanager::TEST_USER . '.userx.shareKey'));
// FIXME: this case currently cannot be distinguished, needs further fixing
/*
$this->assertTrue($this->view->file_exists(
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.userx.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey'));
$this->assertTrue($this->view->file_exists(
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.user1.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey'));
$this->assertTrue($this->view->file_exists(
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.' . Test_Encryption_Keymanager::TEST_USER . '.user1.shareKey'));
*/
// owner key from existing file should still exists because the file is still there
$this->assertTrue($this->view->file_exists(
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey'));
@@ -274,7 +347,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(
@@ -400,18 +473,19 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
$this->assertTrue($this->view->file_exists(
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.user3.shareKey'));
// try to del all share keys froma file, should fail because the file still exists
// try to del all share keys from file, should succeed because the does not exist any more
$result2 = Encryption\Keymanager::delAllShareKeys($this->view, Test_Encryption_Keymanager::TEST_USER, 'folder1/nonexistingFile.txt');
$this->assertTrue($result2);
// check if share keys are really gone
$this->assertFalse($this->view->file_exists(
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/nonexistingFile.txt.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey'));
$this->assertFalse($this->view->file_exists(
// check that it only deleted keys or users who had access, others remain
$this->assertTrue($this->view->file_exists(
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/nonexistingFile.txt.user1.shareKey'));
$this->assertFalse($this->view->file_exists(
$this->assertTrue($this->view->file_exists(
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/nonexistingFile.txt.user2.shareKey'));
$this->assertFalse($this->view->file_exists(
$this->assertTrue($this->view->file_exists(
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/nonexistingFile.txt.user3.shareKey'));
// cleanup
@@ -447,8 +521,8 @@ class TestProtectedKeymanagerMethods extends \OCA\Encryption\Keymanager {
/**
* @param string $sharekey
*/
public static function testGetFilenameFromShareKey($sharekey) {
return self::getFilenameFromShareKey($sharekey);
public static function testGetFilenameFromShareKey($sharekey, $user) {
return self::getFilenameFromShareKey($sharekey, $user);
}
/**

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');

View File

@@ -47,6 +47,7 @@ class Test_Encryption_Proxy extends \PHPUnit_Framework_TestCase {
public $view; // view in /data/user/files
public $rootView; // view on /data/user
public $data;
public $dataLong;
public $filename;
public static function setUpBeforeClass() {
@@ -80,6 +81,7 @@ class Test_Encryption_Proxy extends \PHPUnit_Framework_TestCase {
// init short data
$this->data = 'hats';
$this->dataLong = file_get_contents(__DIR__ . '/../lib/crypt.php');
$this->filename = 'enc_proxy_tests-' . uniqid() . '.txt';
}
@@ -87,6 +89,14 @@ class Test_Encryption_Proxy extends \PHPUnit_Framework_TestCase {
public static function tearDownAfterClass() {
// cleanup test user
\OC_User::deleteUser(\Test_Encryption_Proxy::TEST_ENCRYPTION_PROXY_USER1);
\OC_Hook::clear();
\OC_FileProxy::clearProxies();
// Delete keys in /data/
$view = new \OC\Files\View('/');
$view->rmdir('public-keys');
$view->rmdir('owncloud_private_key');
}
/**
@@ -95,17 +105,19 @@ class Test_Encryption_Proxy extends \PHPUnit_Framework_TestCase {
*/
function testPostFileSize() {
$this->view->file_put_contents($this->filename, $this->data);
$this->view->file_put_contents($this->filename, $this->dataLong);
$size = strlen($this->dataLong);
\OC_FileProxy::$enabled = false;
$unencryptedSize = $this->view->filesize($this->filename);
$encryptedSize = $this->view->filesize($this->filename);
\OC_FileProxy::$enabled = true;
$encryptedSize = $this->view->filesize($this->filename);
$unencryptedSize = $this->view->filesize($this->filename);
$this->assertTrue($encryptedSize !== $unencryptedSize);
$this->assertTrue($encryptedSize > $unencryptedSize);
$this->assertSame($size, $unencryptedSize);
// cleanup
$this->view->unlink($this->filename);
@@ -132,4 +144,42 @@ class Test_Encryption_Proxy extends \PHPUnit_Framework_TestCase {
}
/**
* @dataProvider isExcludedPathProvider
*/
function testIsExcludedPath($path, $expected) {
$this->view->mkdir(dirname($path));
$this->view->file_put_contents($path, "test");
$testClass = new DummyProxy();
$result = $testClass->isExcludedPathTesting($path, $this->userId);
$this->assertSame($expected, $result);
$this->view->deleteAll(dirname($path));
}
public function isExcludedPathProvider() {
return array(
array ('/' . \Test_Encryption_Proxy::TEST_ENCRYPTION_PROXY_USER1 . '/files/test.txt', false),
array (\Test_Encryption_Proxy::TEST_ENCRYPTION_PROXY_USER1 . '/files/test.txt', false),
array ('/files/test.txt', true),
array ('/' . \Test_Encryption_Proxy::TEST_ENCRYPTION_PROXY_USER1 . '/files/versions/test.txt', false),
array ('/' . \Test_Encryption_Proxy::TEST_ENCRYPTION_PROXY_USER1 . '/files_versions/test.txt', false),
array ('/' . \Test_Encryption_Proxy::TEST_ENCRYPTION_PROXY_USER1 . '/files_trashbin/test.txt', true),
array ('/' . \Test_Encryption_Proxy::TEST_ENCRYPTION_PROXY_USER1 . '/file/test.txt', true),
);
}
}
/**
* Dummy class to make protected methods available for testing
*/
class DummyProxy extends \OCA\Encryption\Proxy {
public function isExcludedPathTesting($path, $uid) {
return $this->isExcludedPath($path, $uid);
}
}

View File

@@ -65,8 +65,10 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
// clear share hooks
\OC_Hook::clear('OCP\\Share');
// register share hooks
\OC::registerShareHooks();
\OCP\Util::connectHook('OC_Filesystem', 'setup', '\OC\Files\Storage\Shared', 'setup');
\OCA\Files_Sharing\Helper::registerHooks();
// Sharing related hooks
\OCA\Encryption\Helper::registerShareHooks();
@@ -76,6 +78,7 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
// clear and register hooks
\OC_FileProxy::clearProxies();
\OC_FileProxy::register(new OCA\Files\Share\Proxy());
\OC_FileProxy::register(new OCA\Encryption\Proxy());
// create users
@@ -105,6 +108,9 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
// we don't want to tests with app files_trashbin enabled
\OC_App::disable('files_trashbin');
// login as first user
\Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1);
}
function tearDown() {
@@ -125,6 +131,14 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
\OC_User::deleteUser(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2);
\OC_User::deleteUser(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3);
\OC_User::deleteUser(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER4);
\OC_Hook::clear();
\OC_FileProxy::clearProxies();
// Delete keys in /data/
$view = new \OC\Files\View('/');
$view->rmdir('public-keys');
$view->rmdir('owncloud_private_key');
}
@@ -541,9 +555,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 +1030,122 @@ 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'));
// check that old keys were removed/moved properly
$this->assertFalse($view->file_exists('files_encryption/share-keys' . $folder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
// tear down
\OC\Files\Filesystem::unlink($newFolder);
\OC\Files\Filesystem::unlink('/newfolder');
}
function usersProvider() {
return array(
// test as owner
array(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1),
// test as share receiver
array(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2),
);
}
/**
* @dataProvider usersProvider
*/
function testMoveFileToFolder($userId) {
$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);
$subFolder = $folder . '/subfolder' . uniqid();
\OC\Files\Filesystem::mkdir($subFolder);
// get the file info from previous created file
$fileInfo = \OC\Files\Filesystem::getFileInfo($folder);
$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);
// check that the share keys exist
$this->assertTrue($view->file_exists('files_encryption/share-keys' . $folder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey'));
$this->assertTrue($view->file_exists('files_encryption/share-keys' . $folder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
// move the file into the subfolder as the test user
\Test_Encryption_Util::loginHelper($userId);
\OC\Files\Filesystem::rename($folder . $filename, $subFolder . $filename);
\Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1);
// Get file decrypted contents
$newDecrypt = \OC\Files\Filesystem::file_get_contents($subFolder . $filename);
$this->assertEquals($this->dataShort, $newDecrypt);
// check if additional share key for user2 exists
$this->assertTrue($view->file_exists('files_encryption/share-keys' . $subFolder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey'));
$this->assertTrue($view->file_exists('files_encryption/share-keys' . $subFolder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
// check that old keys were removed/moved properly
$this->assertFalse($view->file_exists('files_encryption/share-keys' . $folder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey'));
$this->assertFalse($view->file_exists('files_encryption/share-keys' . $folder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
// tear down
\OC\Files\Filesystem::unlink($subFolder);
\OC\Files\Filesystem::unlink($folder);
}
}

View File

@@ -96,6 +96,14 @@ class Test_Encryption_Stream extends \PHPUnit_Framework_TestCase {
public static function tearDownAfterClass() {
// cleanup test user
\OC_User::deleteUser(\Test_Encryption_Stream::TEST_ENCRYPTION_STREAM_USER1);
\OC_Hook::clear();
\OC_FileProxy::clearProxies();
// Delete keys in /data/
$view = new \OC\Files\View('/');
$view->rmdir('public-keys');
$view->rmdir('owncloud_private_key');
}
function testStreamOptions() {

View File

@@ -110,6 +110,14 @@ class Test_Encryption_Trashbin extends \PHPUnit_Framework_TestCase {
public static function tearDownAfterClass() {
// cleanup test user
\OC_User::deleteUser(\Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1);
\OC_Hook::clear();
\OC_FileProxy::clearProxies();
// Delete keys in /data/
$view = new \OC\Files\View('/');
$view->rmdir('public-keys');
$view->rmdir('owncloud_private_key');
}
/**
@@ -120,24 +128,33 @@ class Test_Encryption_Trashbin extends \PHPUnit_Framework_TestCase {
// generate filename
$filename = 'tmp-' . uniqid() . '.txt';
$filename2 = $filename . '.backup'; // a second file with similar name
// save file with content
$cryptedFile = file_put_contents('crypt:///' .\Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1. '/files/'. $filename, $this->dataShort);
$cryptedFile2 = file_put_contents('crypt:///' .\Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1. '/files/'. $filename2, $this->dataShort);
// test that data was successfully written
$this->assertTrue(is_int($cryptedFile));
$this->assertTrue(is_int($cryptedFile2));
// check if key for admin exists
$this->assertTrue($this->view->file_exists(
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/keyfiles/' . $filename
. '.key'));
$this->assertTrue($this->view->file_exists(
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/keyfiles/' . $filename2
. '.key'));
// check if share key for admin exists
$this->assertTrue($this->view->file_exists(
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/'
. $filename . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
$this->assertTrue($this->view->file_exists(
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/'
. $filename2 . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
// delete file
// delete first file
\OC\FIles\Filesystem::unlink($filename);
// check if file not exists
@@ -154,6 +171,20 @@ class Test_Encryption_Trashbin extends \PHPUnit_Framework_TestCase {
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/'
. $filename . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
// check that second file still exists
$this->assertTrue($this->view->file_exists(
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files/' . $filename2));
// check that key for second file still exists
$this->assertTrue($this->view->file_exists(
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/keyfiles/' . $filename2
. '.key'));
// check that share key for second file still exists
$this->assertTrue($this->view->file_exists(
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/'
. $filename2 . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
// get files
$trashFiles = $this->view->getDirectoryContent(
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_trashbin/files/');
@@ -179,41 +210,75 @@ class Test_Encryption_Trashbin extends \PHPUnit_Framework_TestCase {
$this->assertTrue($this->view->file_exists(
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_trashbin/share-keys/' . $filename
. '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey.' . $trashFileSuffix));
// return filename for next test
return $filename . '.' . $trashFileSuffix;
}
/**
* @medium
* test restore file
*
* @depends testDeleteFile
*/
function testRestoreFile($filename) {
function testRestoreFile() {
// generate filename
$filename = 'tmp-' . uniqid() . '.txt';
$filename2 = $filename . '.backup'; // a second file with similar name
// save file with content
$cryptedFile = file_put_contents('crypt:///' .\Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1. '/files/'. $filename, $this->dataShort);
$cryptedFile2 = file_put_contents('crypt:///' .\Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1. '/files/'. $filename2, $this->dataShort);
// delete both files
\OC\Files\Filesystem::unlink($filename);
\OC\Files\Filesystem::unlink($filename2);
$trashFiles = $this->view->getDirectoryContent(
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_trashbin/files/');
$trashFileSuffix = null;
$trashFileSuffix2 = null;
// find created file with timestamp
foreach ($trashFiles as $file) {
if (strncmp($file['path'], $filename, strlen($filename))) {
$path_parts = pathinfo($file['name']);
$trashFileSuffix = $path_parts['extension'];
}
if (strncmp($file['path'], $filename2, strlen($filename2))) {
$path_parts = pathinfo($file['name']);
$trashFileSuffix2 = $path_parts['extension'];
}
}
// prepare file information
$path_parts = pathinfo($filename);
$trashFileSuffix = $path_parts['extension'];
$timestamp = str_replace('d', '', $trashFileSuffix);
$fileNameWithoutSuffix = str_replace('.' . $trashFileSuffix, '', $filename);
// restore file
$this->assertTrue(\OCA\Files_Trashbin\Trashbin::restore($filename, $fileNameWithoutSuffix, $timestamp));
// restore first file
$this->assertTrue(\OCA\Files_Trashbin\Trashbin::restore($filename . '.' . $trashFileSuffix, $filename, $timestamp));
// check if file exists
$this->assertTrue($this->view->file_exists(
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files/' . $fileNameWithoutSuffix));
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files/' . $filename));
// check if key for admin exists
$this->assertTrue($this->view->file_exists(
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/keyfiles/'
. $fileNameWithoutSuffix . '.key'));
. $filename . '.key'));
// check if share key for admin exists
$this->assertTrue($this->view->file_exists(
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/'
. $fileNameWithoutSuffix . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
. $filename . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
// check that second file was NOT restored
$this->assertFalse($this->view->file_exists(
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files/' . $filename2));
// check if key for admin exists
$this->assertFalse($this->view->file_exists(
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/keyfiles/'
. $filename2 . '.key'));
// check if share key for admin exists
$this->assertFalse($this->view->file_exists(
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/'
. $filename2 . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
}
/**
@@ -242,7 +307,7 @@ class Test_Encryption_Trashbin extends \PHPUnit_Framework_TestCase {
. $filename . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
// delete file
\OC\FIles\Filesystem::unlink($filename);
\OC\Files\Filesystem::unlink($filename);
// check if file not exists
$this->assertFalse($this->view->file_exists(

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;
@@ -50,16 +53,19 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
\OC_User::clearBackends();
\OC_User::useBackend('database');
// Filesystem related hooks
\OCA\Encryption\Helper::registerFilesystemHooks();
// clear and register hooks
\OC_FileProxy::clearProxies();
\OC_FileProxy::register(new OCA\Encryption\Proxy());
self::setupHooks();
// 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 +122,29 @@ 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);
\OC_Hook::clear();
\OC_FileProxy::clearProxies();
// Delete keys in /data/
$view = new \OC\Files\View('/');
$view->rmdir('public-keys');
$view->rmdir('owncloud_private_key');
}
public static function setupHooks() {
// Filesystem related hooks
\OCA\Encryption\Helper::registerFilesystemHooks();
// clear and register hooks
\OC_FileProxy::clearProxies();
\OC_FileProxy::register(new OCA\Encryption\Proxy());
}
/**
@@ -411,6 +439,48 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
}
/**
* test if all keys get moved to the backup folder correctly
*/
function testBackupAllKeys() {
self::loginHelper(self::TEST_ENCRYPTION_UTIL_USER1);
// create some dummy key files
$encPath = '/' . self::TEST_ENCRYPTION_UTIL_USER1 . '/files_encryption';
$this->view->file_put_contents($encPath . '/keyfiles/foo.key', 'key');
$this->view->file_put_contents($encPath . '/share-keys/foo.user1.shareKey', 'share key');
$util = new \OCA\Encryption\Util($this->view, self::TEST_ENCRYPTION_UTIL_USER1);
$util->backupAllKeys('testing');
$encFolderContent = $this->view->getDirectoryContent($encPath);
$backupPath = '';
foreach ($encFolderContent as $c) {
$name = $c['name'];
if (substr($name, 0, strlen('backup')) === 'backup') {
$backupPath = $encPath . '/'. $c['name'];
break;
}
}
$this->assertTrue($backupPath !== '');
// check backupDir Content
$this->assertTrue($this->view->is_dir($backupPath . '/keyfiles'));
$this->assertTrue($this->view->is_dir($backupPath . '/share-keys'));
$this->assertTrue($this->view->file_exists($backupPath . '/keyfiles/foo.key'));
$this->assertTrue($this->view->file_exists($backupPath . '/share-keys/foo.user1.shareKey'));
$this->assertTrue($this->view->file_exists($backupPath . '/' . self::TEST_ENCRYPTION_UTIL_USER1 . '.private.key'));
$this->assertTrue($this->view->file_exists($backupPath . '/' . self::TEST_ENCRYPTION_UTIL_USER1 . '.public.key'));
//cleanup
$this->view->deleteAll($backupPath);
$this->view->unlink($encPath . '/keyfiles/foo.key', 'key');
$this->view->unlink($encPath . '/share-keys/foo.user1.shareKey', 'share key');
}
function testDescryptAllWithBrokenFiles() {
@@ -537,6 +607,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 +663,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 +680,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);
}
}

View File

@@ -108,6 +108,14 @@ class Test_Encryption_Webdav extends \PHPUnit_Framework_TestCase {
public static function tearDownAfterClass() {
// cleanup test user
\OC_User::deleteUser(\Test_Encryption_Webdav::TEST_ENCRYPTION_WEBDAV_USER1);
\OC_Hook::clear();
\OC_FileProxy::clearProxies();
// Delete keys in /data/
$view = new \OC\Files\View('/');
$view->rmdir('public-keys');
$view->rmdir('owncloud_private_key');
}
/**

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)));

View File

@@ -0,0 +1,26 @@
<?php
OCP\JSON::checkAppEnabled('files_external');
OCP\JSON::callCheck();
OCP\JSON::checkAdminUser();
$pattern = '';
$limit = null;
$offset = null;
if (isset($_GET['pattern'])) {
$pattern = $_GET['pattern'];
}
if (isset($_GET['limit'])) {
$limit = $_GET['limit'];
}
if (isset($_GET['offset'])) {
$offset = $_GET['offset'];
}
$groups = \OC_Group::getGroups($pattern, $limit, $offset);
$users = \OCP\User::getDisplayNames($pattern, $limit, $offset);
$results = array('groups' => $groups, 'users' => $users);
\OCP\JSON::success($results);

View File

@@ -2,12 +2,20 @@
<info>
<id>files_external</id>
<name>External storage support</name>
<description>Mount external storage sources</description>
<description>
This application enables administrators to configure connections to external storage providers, such as FTP servers, S3 or SWIFT object stores, Google Drive, Dropbox, other ownCloud servers, WebDAV servers and more. Administrators can choose in the GUI which type of storage to enable, and can mount these storage locations for a user, a group, or the entire system. Users will see a new folder appear in their root ownCloud directory, and then can then access and use it like any other ownCloud folder. External Storage also allows users to share files stored in these external location. In these cases, the credentials for the owner of the file are used then the recipient requests the file from external storage, thereby ensuring that the recipient can get at the file that was shared.
In addition to the GUI, it is possible to configure external storage manually at the command line. This option provides the advanced user with more flexibility for configuring bulk external storage mounts, as well as setting mount priorities. More information is available in the External Storage GUI documentation and the External Storage Configuration File documentation.
</description>
<licence>AGPL</licence>
<author>Robin Appelman, Michael Gapczynski, Vincent Petry</author>
<requiremin>4.93</requiremin>
<shipped>true</shipped>
<documentation>
<admin>admin-external-storage</admin>
</documentation>
<types>
<filesystem/>
</types>
<ocsid>166048</ocsid>
</info>

View File

@@ -20,8 +20,10 @@
*
*/
OC_API::register('get',
'/apps/files_external/api/v1/mounts',
array('\OCA\Files\External\Api', 'getUserMounts'),
'files_external');
$this->create('files_external_list_applicable', '/applicable')->actionInclude('files_external/ajax/applicable.php');
OC_API::register('get',
'/apps/files_external/api/v1/mounts',
array('\OCA\Files\External\Api', 'getUserMounts'),
'files_external');

View File

@@ -1 +1 @@
0.2
0.2.2

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;
@@ -29,3 +42,19 @@ tr:hover>td.remove>img { visibility:visible; cursor:pointer; }
#userMountingBackends {
padding-left: 25px;
}
#body-settings .select2-results .select2-result-label {
height: 32px;
padding: 3px;
}
.select2-results .select2-result-label .avatardiv {
display:inline-block;
}
.select2-results .select2-result-label .avatardiv + span {
margin-left: 10px;
}
.select2-results .select2-result-label .avatardiv[data-type="group"] + span {
vertical-align: top;
top: 6px;
position: relative;
}

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;
});

View File

@@ -11,9 +11,18 @@ function updateStatus(statusEl, result){
}
}
function getSelection($row) {
var values = $row.find('.applicableUsers').select2('val');
if (!values || values.length === 0) {
values = ['all'];
}
return values;
}
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;
}
@@ -41,10 +50,7 @@ OC.MountConfig={
}
});
if ($('#externalStorage').data('admin') === true) {
var multiselect = $(tr).find('.chzn-select').val();
if (multiselect == null) {
return false;
}
var multiselect = getSelection($(tr));
}
if (addMountPoint) {
var status = false;
@@ -80,9 +86,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 +147,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);
@@ -161,8 +171,139 @@ OC.MountConfig={
};
$(document).ready(function() {
$('.chzn-select').chosen();
//initialize hidden input field with list of users and groups
$('#externalStorage').find('tr:not(#addMountPoint)').each(function(i,tr) {
var applicable = $(tr).find('.applicable');
if (applicable.length > 0) {
var groups = applicable.data('applicable-groups');
var groupsId = [];
$.each(groups, function () {
groupsId.push(this+"(group)");
});
var users = applicable.data('applicable-users');
if (users.indexOf('all') > -1) {
$(tr).find('.applicableUsers').val('');
} else {
$(tr).find('.applicableUsers').val(groupsId.concat(users).join(','));
}
}
});
var userListLimit = 30;
function addSelect2 ($elements) {
if ($elements.length > 0) {
$elements.select2({
placeholder: t('files_external', 'All users. Type to select user or group.'),
allowClear: true,
multiple: true,
//minimumInputLength: 1,
ajax: {
url: OC.generateUrl('apps/files_external/applicable'),
dataType: 'json',
quietMillis: 100,
data: function (term, page) { // page is the one-based page number tracked by Select2
return {
pattern: term, //search term
limit: userListLimit, // page size
offset: userListLimit*(page-1) // page number starts with 0
};
},
results: function (data, page) {
if (data.status === "success") {
var results = [];
var userCount = 0; // users is an object
// add groups
$.each(data.groups, function(i, group) {
results.push({name:group+'(group)', displayname:group, type:'group' });
});
// add users
$.each(data.users, function(id, user) {
userCount++;
results.push({name:id, displayname:user, type:'user' });
});
var more = (userCount >= userListLimit) || (data.groups.length >= userListLimit);
return {results: results, more: more};
} else {
//FIXME add error handling
}
}
},
initSelection: function(element, callback) {
var promises = [];
var results = [];
$(element.val().split(",")).each(function (i,userId) {
var def = new $.Deferred();
promises.push(def.promise());
var pos = userId.indexOf('(group)');
if (pos !== -1) {
//add as group
results.push({name:userId, displayname:userId.substr(0, pos), type:'group'});
def.resolve();
} else {
$.ajax(OC.generateUrl('apps/files_external/applicable'), {
data: {
pattern: userId
},
dataType: "json"
}).done(function(data) {
if (data.status === "success") {
if (data.users[userId]) {
results.push({name:userId, displayname:data.users[userId], type:'user'});
}
def.resolve();
} else {
//FIXME add error handling
}
});
}
});
$.when.apply(undefined, promises).then(function(){
callback(results);
});
},
id: function(element) {
return element.name;
},
formatResult: function (element) {
var $result = $('<span><div class="avatardiv"/><span>'+escapeHTML(element.displayname)+'</span></span>');
var $div = $result.find('.avatardiv')
.attr('data-type', element.type)
.attr('data-name', element.name)
.attr('data-displayname', element.displayname);
if (element.type === 'group') {
var url = OC.imagePath('core','places/contacts-dark'); // TODO better group icon
$div.html('<img width="32" height="32" src="'+url+'">');
}
return $result.get(0).outerHTML;
},
formatSelection: function (element) {
if (element.type === 'group') {
return '<span title="'+escapeHTML(element.name)+'" class="group">'+escapeHTML(element.displayname+' '+t('files_external', '(group)'))+'</span>';
} else {
return '<span title="'+escapeHTML(element.name)+'" class="user">'+escapeHTML(element.displayname)+'</span>';
}
},
escapeMarkup: function (m) { return m; } // we escape the markup in formatResult and formatSelection
}).on("select2-loaded", function() {
$.each($(".avatardiv"), function(i, div) {
$div = $(div);
if ($div.data('type') === 'user') {
$div.avatar($div.data('name'),32);
}
})
});
}
}
addSelect2($('tr:not(#addMountPoint) .applicableUsers'));
$('#externalStorage').on('change', '#selectBackend', function() {
var tr = $(this).parent().parent();
$('#externalStorage tbody').append($(tr).clone());
@@ -187,15 +328,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) {
@@ -204,15 +345,11 @@ $(document).ready(function() {
return false;
}
});
// Reset chosen
var chosen = $(tr).find('.applicable select');
chosen.parent().find('div').remove();
chosen.removeAttr('id').removeClass('chzn-done').css({display:'inline-block'});
chosen.chosen();
$(tr).find('td').last().attr('class', 'remove');
$(tr).find('td').last().removeAttr('style');
$(tr).removeAttr('id');
$(this).remove();
addSelect2(tr.find('.applicableUsers'));
});
function suggestMountPoint(defaultMountPoint) {
@@ -265,8 +402,8 @@ $(document).ready(function() {
OC.MountConfig.saveStorage($(this).parent().parent().parent());
});
$('#externalStorage').on('change', '.applicable .chzn-select', function() {
OC.MountConfig.saveStorage($(this).parent().parent());
$('#externalStorage').on('change', '.applicable', function() {
OC.MountConfig.saveStorage($(this).parent());
});
$('#sslCertificate').on('click', 'td.remove>img', function() {
@@ -283,20 +420,18 @@ $(document).ready(function() {
if ($('#externalStorage').data('admin') === true) {
var isPersonal = false;
var multiselect = $(tr).find('.chzn-select').val();
if (multiselect != null) {
$.each(multiselect, function(index, value) {
var pos = value.indexOf('(group)');
if (pos != -1) {
var mountType = 'group';
var applicable = value.substr(0, pos);
} else {
var mountType = 'user';
var applicable = value;
}
$.post(OC.filePath('files_external', 'ajax', 'removeMountPoint.php'), { mountPoint: mountPoint, mountType: mountType, applicable: applicable, isPersonal: isPersonal });
});
}
var multiselect = getSelection($(tr));
$.each(multiselect, function(index, value) {
var pos = value.indexOf('(group)');
if (pos != -1) {
var mountType = 'group';
var applicable = value.substr(0, pos);
} else {
var mountType = 'user';
var applicable = value;
}
$.post(OC.filePath('files_external', 'ajax', 'removeMountPoint.php'), { mountPoint: mountPoint, mountType: mountType, applicable: applicable, isPersonal: isPersonal });
});
} else {
var mountType = 'user';
var applicable = OC.currentUser;

View File

@@ -45,6 +45,10 @@ class AmazonS3 extends \OC\Files\Storage\Common {
* @var array
*/
private static $tmpFiles = array();
/**
* @var array
*/
private $params;
/**
* @var bool
*/
@@ -53,9 +57,14 @@ class AmazonS3 extends \OC\Files\Storage\Common {
* @var int
*/
private $timeout = 15;
/**
* @var int in seconds
*/
private $rescanDelay = 10;
/**
* @param string $path
* @return string correctly encoded path
*/
private function normalizePath($path) {
$path = trim($path, '/');
@@ -67,67 +76,92 @@ class AmazonS3 extends \OC\Files\Storage\Common {
return $path;
}
/**
* when running the tests wait to let the buckets catch up
*/
private function testTimeout() {
if ($this->test) {
sleep($this->timeout);
}
}
private function isRoot($path) {
return $path === '.';
}
private function cleanKey($path) {
if ($this->isRoot($path)) {
return '/';
}
return $path;
}
public function __construct($params) {
if (!isset($params['key']) || !isset($params['secret']) || !isset($params['bucket'])) {
if (empty($params['key']) || empty($params['secret']) || empty($params['bucket'])) {
throw new \Exception("Access Key, Secret and Bucket have to be configured.");
}
$this->id = 'amazon::' . $params['key'] . md5($params['secret']);
$this->id = 'amazon::' . $params['bucket'];
$this->updateLegacyId($params);
$this->bucket = $params['bucket'];
$scheme = ($params['use_ssl'] === 'false') ? 'http' : 'https';
$this->test = isset($params['test']);
$this->timeout = ( ! isset($params['timeout'])) ? 15 : $params['timeout'];
$params['region'] = ( ! isset($params['region']) || $params['region'] === '' ) ? 'eu-west-1' : $params['region'];
$params['hostname'] = ( !isset($params['hostname']) || $params['hostname'] === '' ) ? 's3.amazonaws.com' : $params['hostname'];
$this->timeout = (!isset($params['timeout'])) ? 15 : $params['timeout'];
$this->rescanDelay = (!isset($params['rescanDelay'])) ? 10 : $params['rescanDelay'];
$params['region'] = empty($params['region']) ? 'eu-west-1' : $params['region'];
$params['hostname'] = empty($params['hostname']) ? 's3.amazonaws.com' : $params['hostname'];
if (!isset($params['port']) || $params['port'] === '') {
$params['port'] = ($params['use_ssl'] === 'false') ? 80 : 443;
}
$base_url = $scheme.'://'.$params['hostname'].':'.$params['port'].'/';
$this->params = $params;
}
$this->connection = S3Client::factory(array(
'key' => $params['key'],
'secret' => $params['secret'],
'base_url' => $base_url,
'region' => $params['region']
));
/**
* Updates old storage ids (v0.2.1 and older) that are based on key and secret to new ones based on the bucket name.
* TODO Do this in an update.php. requires iterating over all users and loading the mount.json from their home
*
* @param array $params
*/
public function updateLegacyId (array $params) {
$oldId = 'amazon::' . $params['key'] . md5($params['secret']);
if (!$this->connection->isValidBucketName($this->bucket)) {
throw new \Exception("The configured bucket name is invalid.");
// find by old id or bucket
$stmt = \OC::$server->getDatabaseConnection()->prepare(
'SELECT `numeric_id`, `id` FROM `*PREFIX*storages` WHERE `id` IN (?, ?)'
);
$stmt->execute(array($oldId, $this->id));
while ($row = $stmt->fetch()) {
$storages[$row['id']] = $row['numeric_id'];
}
if (!$this->connection->doesBucketExist($this->bucket)) {
try {
$result = $this->connection->createBucket(array(
'Bucket' => $this->bucket
));
$this->connection->waitUntilBucketExists(array(
'Bucket' => $this->bucket,
'waiter.interval' => 1,
'waiter.max_attempts' => 15
));
$this->testTimeout();
} catch (S3Exception $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
throw new \Exception("Creation of bucket failed.");
}
if (isset($storages[$this->id]) && isset($storages[$oldId])) {
// if both ids exist, delete the old storage and corresponding filecache entries
\OC\Files\Cache\Storage::remove($oldId);
} else if (isset($storages[$oldId])) {
// if only the old id exists do an update
$stmt = \OC::$server->getDatabaseConnection()->prepare(
'UPDATE `*PREFIX*storages` SET `id` = ? WHERE `id` = ?'
);
$stmt->execute(array($this->id, $oldId));
}
// only the bucket based id may exist, do nothing
}
if (!$this->file_exists('.')) {
$result = $this->connection->putObject(array(
'Bucket' => $this->bucket,
'Key' => '.',
'Body' => '',
'ContentType' => 'httpd/unix-directory',
'ContentLength' => 0
));
$this->testTimeout();
/**
* Remove a file or folder
*
* @param string $path
* @return bool
*/
protected function remove($path) {
// remember fileType to reduce http calls
$fileType = $this->filetype($path);
if ($fileType === 'dir') {
return $this->rmdir($path);
} else if ($fileType === 'file') {
return $this->unlink($path);
} else {
return false;
}
}
@@ -139,16 +173,14 @@ class AmazonS3 extends \OC\Files\Storage\Common {
}
try {
$result = $this->connection->putObject(array(
$this->getConnection()->putObject(array(
'Bucket' => $this->bucket,
'Key' => $path . '/',
'Body' => '',
'ContentType' => 'httpd/unix-directory',
'ContentLength' => 0
'Key' => $path . '/',
'ContentType' => 'httpd/unix-directory'
));
$this->testTimeout();
} catch (S3Exception $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::logException('files_external', $e);
return false;
}
@@ -156,77 +188,75 @@ class AmazonS3 extends \OC\Files\Storage\Common {
}
public function file_exists($path) {
$path = $this->normalizePath($path);
if (!$path) {
$path = '.';
} else if ($path != '.' && $this->is_dir($path)) {
$path .= '/';
}
try {
$result = $this->connection->doesObjectExist(
$this->bucket,
$path
);
} catch (S3Exception $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
return false;
}
return $result;
return $this->filetype($path) !== false;
}
public function rmdir($path) {
$path = $this->normalizePath($path);
if ($this->isRoot($path)) {
return $this->clearBucket();
}
if (!$this->file_exists($path)) {
return false;
}
$dh = $this->opendir($path);
if(is_resource($dh)) {
while (($file = readdir($dh)) !== false) {
if ($file === '.' || $file === '..') {
continue;
}
if ($this->is_dir($path . '/' . $file)) {
$this->rmdir($path . '/' . $file);
} else {
$this->unlink($path . '/' . $file);
}
}
}
return $this->batchDelete($path);
}
protected function clearBucket() {
try {
$result = $this->connection->deleteObject(array(
'Bucket' => $this->bucket,
'Key' => $path . '/'
));
$this->testTimeout();
$this->getConnection()->clearBucket($this->bucket);
return true;
// clearBucket() is not working with Ceph, so if it fails we try the slower approach
} catch (\Exception $e) {
return $this->batchDelete();
}
return false;
}
private function batchDelete ($path = null) {
$params = array(
'Bucket' => $this->bucket
);
if ($path !== null) {
$params['Prefix'] = $path . '/';
}
try {
// Since there are no real directories on S3, we need
// to delete all objects prefixed with the path.
do {
// instead of the iterator, manually loop over the list ...
$objects = $this->getConnection()->listObjects($params);
// ... so we can delete the files in batches
$this->getConnection()->deleteObjects(array(
'Bucket' => $this->bucket,
'Objects' => $objects['Contents']
));
$this->testTimeout();
// we reached the end when the list is no longer truncated
} while ($objects['IsTruncated']);
} catch (S3Exception $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::logException('files_external', $e);
return false;
}
return true;
}
public function opendir($path) {
$path = $this->normalizePath($path);
if ($path === '.') {
if ($this->isRoot($path)) {
$path = '';
} else if ($path) {
} else {
$path .= '/';
}
try {
$files = array();
$result = $this->connection->getIterator('ListObjects', array(
$result = $this->getConnection()->getIterator('ListObjects', array(
'Bucket' => $this->bucket,
'Delimiter' => '/',
'Prefix' => $path
@@ -246,7 +276,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
return opendir('fakedir://amazons3' . $path);
} catch (S3Exception $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::logException('files_external', $e);
return false;
}
}
@@ -255,27 +285,29 @@ class AmazonS3 extends \OC\Files\Storage\Common {
$path = $this->normalizePath($path);
try {
if ($this->is_dir($path) && $path != '.') {
$path .= '/';
}
$result = $this->connection->headObject(array(
'Bucket' => $this->bucket,
'Key' => $path
));
$stat = array();
$stat['size'] = $result['ContentLength'] ? $result['ContentLength'] : 0;
if ($result['Metadata']['lastmodified']) {
$stat['mtime'] = strtotime($result['Metadata']['lastmodified']);
if ($this->is_dir($path)) {
//folders don't really exist
$stat['size'] = -1; //unknown
$stat['mtime'] = time() - $this->rescanDelay * 1000;
} else {
$stat['mtime'] = strtotime($result['LastModified']);
$result = $this->getConnection()->headObject(array(
'Bucket' => $this->bucket,
'Key' => $path
));
$stat['size'] = $result['ContentLength'] ? $result['ContentLength'] : 0;
if ($result['Metadata']['lastmodified']) {
$stat['mtime'] = strtotime($result['Metadata']['lastmodified']);
} else {
$stat['mtime'] = strtotime($result['LastModified']);
}
}
$stat['atime'] = time();
return $stat;
} catch(S3Exception $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::logException('files_external', $e);
return false;
}
}
@@ -283,20 +315,19 @@ class AmazonS3 extends \OC\Files\Storage\Common {
public function filetype($path) {
$path = $this->normalizePath($path);
if ($this->isRoot($path)) {
return 'dir';
}
try {
if ($path != '.' && $this->connection->doesObjectExist($this->bucket, $path)) {
if ($this->getConnection()->doesObjectExist($this->bucket, $path)) {
return 'file';
}
if ($path != '.') {
$path .= '/';
}
if ($this->connection->doesObjectExist($this->bucket, $path)) {
if ($this->getConnection()->doesObjectExist($this->bucket, $path.'/')) {
return 'dir';
}
} catch (S3Exception $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::logException('files_external', $e);
return false;
}
@@ -306,14 +337,18 @@ class AmazonS3 extends \OC\Files\Storage\Common {
public function unlink($path) {
$path = $this->normalizePath($path);
if ($this->is_dir($path)) {
return $this->rmdir($path);
}
try {
$result = $this->connection->deleteObject(array(
$this->getConnection()->deleteObject(array(
'Bucket' => $this->bucket,
'Key' => $path
));
$this->testTimeout();
} catch (S3Exception $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::logException('files_external', $e);
return false;
}
@@ -330,13 +365,13 @@ class AmazonS3 extends \OC\Files\Storage\Common {
self::$tmpFiles[$tmpFile] = $path;
try {
$result = $this->connection->getObject(array(
$this->getConnection()->getObject(array(
'Bucket' => $this->bucket,
'Key' => $path,
'SaveAs' => $tmpFile
));
} catch (S3Exception $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::logException('files_external', $e);
return false;
}
@@ -378,12 +413,12 @@ class AmazonS3 extends \OC\Files\Storage\Common {
return 'httpd/unix-directory';
} else if ($this->file_exists($path)) {
try {
$result = $this->connection->headObject(array(
$result = $this->getConnection()->headObject(array(
'Bucket' => $this->bucket,
'Key' => $path
));
} catch (S3Exception $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::logException('files_external', $e);
return false;
}
@@ -400,28 +435,31 @@ class AmazonS3 extends \OC\Files\Storage\Common {
$metadata = array('lastmodified' => $mtime);
}
$fileType = $this->filetype($path);
try {
if ($this->file_exists($path)) {
if ($this->is_dir($path) && $path != '.') {
if ($fileType !== false) {
if ($fileType === 'dir' && ! $this->isRoot($path)) {
$path .= '/';
}
$result = $this->connection->copyObject(array(
$this->getConnection()->copyObject(array(
'Bucket' => $this->bucket,
'Key' => $path,
'Key' => $this->cleanKey($path),
'Metadata' => $metadata,
'CopySource' => $this->bucket . '/' . $path
));
$this->testTimeout();
} else {
$result = $this->connection->putObject(array(
$mimeType = \OC_Helper::getMimetypeDetector()->detectPath($path);
$this->getConnection()->putObject(array(
'Bucket' => $this->bucket,
'Key' => $path,
'Metadata' => $metadata
'Key' => $this->cleanKey($path),
'Metadata' => $metadata,
'ContentType' => $mimeType
));
$this->testTimeout();
}
} catch (S3Exception $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::logException('files_external', $e);
return false;
}
@@ -434,35 +472,33 @@ class AmazonS3 extends \OC\Files\Storage\Common {
if ($this->is_file($path1)) {
try {
$result = $this->connection->copyObject(array(
$this->getConnection()->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) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::logException('files_external', $e);
return false;
}
} else {
if ($this->file_exists($path2)) {
return false;
}
$this->remove($path2);
try {
$result = $this->connection->copyObject(array(
$this->getConnection()->copyObject(array(
'Bucket' => $this->bucket,
'Key' => $path2 . '/',
'CopySource' => $this->bucket . '/' . $path1 . '/'
'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1 . '/')
));
$this->testTimeout();
} catch (S3Exception $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::logException('files_external', $e);
return false;
}
$dh = $this->opendir($path1);
if(is_resource($dh)) {
if (is_resource($dh)) {
while (($file = readdir($dh)) !== false) {
if ($file === '.' || $file === '..') {
continue;
@@ -483,6 +519,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
$path2 = $this->normalizePath($path2);
if ($this->is_file($path1)) {
if ($this->copy($path1, $path2) === false) {
return false;
}
@@ -492,9 +529,6 @@ class AmazonS3 extends \OC\Files\Storage\Common {
return false;
}
} else {
if ($this->file_exists($path2)) {
return false;
}
if ($this->copy($path1, $path2) === false) {
return false;
@@ -510,7 +544,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
}
public function test() {
$test = $this->connection->getBucketAcl(array(
$test = $this->getConnection()->getBucketAcl(array(
'Bucket' => $this->bucket,
));
if (isset($test) && !is_null($test->getPath('Owner/ID'))) {
@@ -523,7 +557,48 @@ class AmazonS3 extends \OC\Files\Storage\Common {
return $this->id;
}
/**
* Returns the connection
*
* @return S3Client connected client
* @throws \Exception if connection could not be made
*/
public function getConnection() {
if (!is_null($this->connection)) {
return $this->connection;
}
$scheme = ($this->params['use_ssl'] === 'false') ? 'http' : 'https';
$base_url = $scheme . '://' . $this->params['hostname'] . ':' . $this->params['port'] . '/';
$this->connection = S3Client::factory(array(
'key' => $this->params['key'],
'secret' => $this->params['secret'],
'base_url' => $base_url,
'region' => $this->params['region']
));
if (!$this->connection->isValidBucketName($this->bucket)) {
throw new \Exception("The configured bucket name is invalid.");
}
if (!$this->connection->doesBucketExist($this->bucket)) {
try {
$this->connection->createBucket(array(
'Bucket' => $this->bucket
));
$this->connection->waitUntilBucketExists(array(
'Bucket' => $this->bucket,
'waiter.interval' => 1,
'waiter.max_attempts' => 15
));
$this->testTimeout();
} catch (S3Exception $e) {
\OCP\Util::logException('files_external', $e);
throw new \Exception('Creation of bucket failed. '.$e->getMessage());
}
}
return $this->connection;
}
@@ -533,9 +608,9 @@ class AmazonS3 extends \OC\Files\Storage\Common {
}
try {
$result= $this->connection->putObject(array(
$this->getConnection()->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)
@@ -544,7 +619,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
unlink($tmpFile);
} catch (S3Exception $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::logException('files_external', $e);
return false;
}
}

46
apps/files_external/lib/config.php Executable file → Normal file
View File

@@ -118,6 +118,22 @@ class OC_Mount_Config {
}
$manager->addMount($mount);
}
if ($data['user']) {
$user = \OC::$server->getUserManager()->get($data['user']);
$userView = new \OC\Files\View('/' . $user->getUID() . '/files');
$changePropagator = new \OC\Files\Cache\ChangePropagator($userView);
$etagPropagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, \OC::$server->getConfig());
$etagPropagator->propagateDirtyMountPoints();
\OCP\Util::connectHook(
\OC\Files\Filesystem::CLASSNAME,
\OC\Files\Filesystem::signal_create_mount,
$etagPropagator, 'updateHook');
\OCP\Util::connectHook(
\OC\Files\Filesystem::CLASSNAME,
\OC\Files\Filesystem::signal_delete_mount,
$etagPropagator, 'updateHook');
}
}
/**
@@ -169,6 +185,7 @@ class OC_Mount_Config {
foreach ($options as &$option) {
$option = self::setUserVars($user, $option);
}
$options['personal'] = false;
$options['options'] = self::decryptPasswords($options['options']);
if (!isset($options['priority'])) {
$options['priority'] = $backends[$options['class']]['priority'];
@@ -458,6 +475,7 @@ class OC_Mount_Config {
$priority = null) {
$backends = self::getBackends();
$mountPoint = OC\Files\Filesystem::normalizePath($mountPoint);
$relMountPoint = $mountPoint;
if ($mountPoint === '' || $mountPoint === '/') {
// can't mount at root folder
return false;
@@ -490,6 +508,10 @@ class OC_Mount_Config {
}
$mountPoints = self::readData($isPersonal ? OCP\User::getUser() : NULL);
// who else loves multi-dimensional array ?
$isNew = !isset($mountPoints[$mountType]) ||
!isset($mountPoints[$mountType][$applicable]) ||
!isset($mountPoints[$mountType][$applicable][$mountPoint]);
$mountPoints = self::mergeMountPoints($mountPoints, $mount, $mountType);
// Set default priority if none set
@@ -505,7 +527,19 @@ class OC_Mount_Config {
self::writeData($isPersonal ? OCP\User::getUser() : NULL, $mountPoints);
return self::getBackendStatus($class, $classOptions, $isPersonal);
$result = self::getBackendStatus($class, $classOptions, $isPersonal);
if ($result && $isNew) {
\OC_Hook::emit(
\OC\Files\Filesystem::CLASSNAME,
\OC\Files\Filesystem::signal_create_mount,
array(
\OC\Files\Filesystem::signal_param_path => $relMountPoint,
\OC\Files\Filesystem::signal_param_mount_type => $mountType,
\OC\Files\Filesystem::signal_param_users => $applicable,
)
);
}
return $result;
}
/**
@@ -518,6 +552,7 @@ class OC_Mount_Config {
*/
public static function removeMountPoint($mountPoint, $mountType, $applicable, $isPersonal = false) {
// Verify that the mount point applies for the current user
$relMountPoints = $mountPoint;
if ($isPersonal) {
if ($applicable != OCP\User::getUser()) {
return false;
@@ -538,6 +573,15 @@ class OC_Mount_Config {
}
}
self::writeData($isPersonal ? OCP\User::getUser() : NULL, $mountPoints);
\OC_Hook::emit(
\OC\Files\Filesystem::CLASSNAME,
\OC\Files\Filesystem::signal_delete_mount,
array(
\OC\Files\Filesystem::signal_param_path => $relMountPoints,
\OC\Files\Filesystem::signal_param_mount_type => $mountType,
\OC\Files\Filesystem::signal_param_users => $applicable,
)
);
return true;
}

1
apps/files_external/lib/dropbox.php Executable file → Normal file
View File

@@ -44,6 +44,7 @@ class Dropbox extends \OC\Files\Storage\Common {
$this->id = 'dropbox::'.$params['app_key'] . $params['token']. '/' . $this->root;
$oauth = new \Dropbox_OAuth_Curl($params['app_key'], $params['app_secret']);
$oauth->setToken($params['token'], $params['token_secret']);
// note: Dropbox_API connection is lazy
$this->dropbox = new \Dropbox_API($oauth, 'auto');
} else {
throw new \Exception('Creating \OC\Files\Storage\Dropbox storage failed');

View File

@@ -0,0 +1,126 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCA\Files_External;
use OC\Files\Filesystem;
/**
* Updates the etag of parent folders whenever a new external storage mount
* point has been created or deleted. Updates need to be triggered using
* the updateHook() method.
*
* There are two modes of operation:
* - for personal mount points, the etag is propagated directly
* - for system mount points, a dirty flag is saved in the configuration and
* the etag will be updated the next time propagateDirtyMountPoints() is called
*/
class EtagPropagator {
/**
* @var \OCP\IUser
*/
protected $user;
/**
* @var \OC\Files\Cache\ChangePropagator
*/
protected $changePropagator;
/**
* @var \OCP\IConfig
*/
protected $config;
/**
* @param \OCP\IUser $user current user, must match the propagator's
* user
* @param \OC\Files\Cache\ChangePropagator $changePropagator change propagator
* initialized with a view for $user
* @param \OCP\IConfig $config
*/
public function __construct($user, $changePropagator, $config) {
$this->user = $user;
$this->changePropagator = $changePropagator;
$this->config = $config;
}
/**
* Propagate the etag changes for all mountpoints marked as dirty and mark the mountpoints as clean
*
* @param int $time
*/
public function propagateDirtyMountPoints($time = null) {
if ($time === null) {
$time = time();
}
$mountPoints = $this->getDirtyMountPoints();
foreach ($mountPoints as $mountPoint) {
$this->changePropagator->addChange($mountPoint);
$this->config->setUserValue($this->user->getUID(), 'files_external', $mountPoint, $time);
}
if (count($mountPoints)) {
$this->changePropagator->propagateChanges($time);
}
}
/**
* Get all mountpoints we need to update the etag for
*
* @return string[]
*/
protected function getDirtyMountPoints() {
$dirty = array();
$mountPoints = $this->config->getAppKeys('files_external');
foreach ($mountPoints as $mountPoint) {
if (substr($mountPoint, 0, 1) === '/') {
$updateTime = $this->config->getAppValue('files_external', $mountPoint);
$userTime = $this->config->getUserValue($this->user->getUID(), 'files_external', $mountPoint);
if ($updateTime > $userTime) {
$dirty[] = $mountPoint;
}
}
}
return $dirty;
}
/**
* @param string $mountPoint
* @param int $time
*/
protected function markDirty($mountPoint, $time = null) {
if ($time === null) {
$time = time();
}
$this->config->setAppValue('files_external', $mountPoint, $time);
}
/**
* Update etags for mount points for known user
* For global or group mount points, updating the etag for every user is not feasible
* instead we mark the mount point as dirty and update the etag when the filesystem is loaded for the user
* For personal mount points, the change is propagated directly
*
* @param array $params hook parameters
* @param int $time update time to use when marking a mount point as dirty
*/
public function updateHook($params, $time = null) {
if ($time === null) {
$time = time();
}
$users = $params[Filesystem::signal_param_users];
$type = $params[Filesystem::signal_param_mount_type];
$mountPoint = $params[Filesystem::signal_param_path];
$mountPoint = Filesystem::normalizePath($mountPoint);
if ($type === \OC_Mount_Config::MOUNT_TYPE_GROUP or $users === 'all') {
$this->markDirty($mountPoint, $time);
} else {
$this->changePropagator->addChange($mountPoint);
$this->changePropagator->propagateChanges($time);
}
}
}

View File

@@ -39,7 +39,7 @@ class FTP extends \OC\Files\Storage\StreamWrapper{
$this->root .= '/';
}
} else {
throw new \Exception();
throw new \Exception('Creating \OC\Files\Storage\FTP storage failed');
}
}

View File

@@ -52,6 +52,7 @@ class Google extends \OC\Files\Storage\Common {
$client->setScopes(array('https://www.googleapis.com/auth/drive'));
$client->setUseObjects(true);
$client->setAccessToken($params['token']);
// note: API connection is lazy
$this->service = new \Google_DriveService($client);
$token = json_decode($params['token'], true);
$this->id = 'google::'.substr($params['client_id'], 0, 30).$token['created'];

View File

@@ -22,6 +22,14 @@ class OwnCloud extends \OC\Files\Storage\DAV{
// extract context path from host if specified
// (owncloud install path on host)
$host = $params['host'];
// strip protocol
if (substr($host, 0, 8) == "https://") {
$host = substr($host, 8);
$params['secure'] = true;
} else if (substr($host, 0, 7) == "http://") {
$host = substr($host, 7);
$params['secure'] = false;
}
$contextPath = '';
$hostSlashPos = strpos($host, '/');
if ($hostSlashPos !== false){

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) {
@@ -35,16 +53,24 @@ class SFTP extends \OC\Files\Storage\Common {
if (substr($this->root, -1, 1) != '/') {
$this->root .= '/';
}
}
/**
* Returns the connection.
*
* @return \Net_SFTP connected client instance
* @throws \Exception when the connection failed
*/
public function getConnection() {
if (!is_null($this->client)) {
return $this->client;
}
$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 +79,11 @@ 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');
}
return $this->client;
}
public function test() {
@@ -63,7 +94,7 @@ class SFTP extends \OC\Files\Storage\Common {
) {
return false;
}
return $this->client->nlist() !== false;
return $this->getConnection()->nlist() !== false;
}
public function getId(){
@@ -131,7 +162,7 @@ class SFTP extends \OC\Files\Storage\Common {
public function mkdir($path) {
try {
return $this->client->mkdir($this->absPath($path));
return $this->getConnection()->mkdir($this->absPath($path));
} catch (\Exception $e) {
return false;
}
@@ -139,7 +170,7 @@ class SFTP extends \OC\Files\Storage\Common {
public function rmdir($path) {
try {
return $this->client->delete($this->absPath($path), true);
return $this->getConnection()->delete($this->absPath($path), true);
} catch (\Exception $e) {
return false;
}
@@ -147,7 +178,7 @@ class SFTP extends \OC\Files\Storage\Common {
public function opendir($path) {
try {
$list = $this->client->nlist($this->absPath($path));
$list = $this->getConnection()->nlist($this->absPath($path));
$id = md5('sftp:' . $path);
$dirStream = array();
@@ -165,7 +196,7 @@ class SFTP extends \OC\Files\Storage\Common {
public function filetype($path) {
try {
$stat = $this->client->stat($this->absPath($path));
$stat = $this->getConnection()->stat($this->absPath($path));
if ($stat['type'] == NET_SFTP_TYPE_REGULAR) {
return 'file';
}
@@ -181,7 +212,7 @@ class SFTP extends \OC\Files\Storage\Common {
public function file_exists($path) {
try {
return $this->client->stat($this->absPath($path)) !== false;
return $this->getConnection()->stat($this->absPath($path)) !== false;
} catch (\Exception $e) {
return false;
}
@@ -189,7 +220,7 @@ class SFTP extends \OC\Files\Storage\Common {
public function unlink($path) {
try {
return $this->client->delete($this->absPath($path), true);
return $this->getConnection()->delete($this->absPath($path), true);
} catch (\Exception $e) {
return false;
}
@@ -216,8 +247,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->getConnection())));
return fopen($this->constructUrl($path), $mode, false, $context);
}
} catch (\Exception $e) {
}
@@ -230,7 +261,7 @@ class SFTP extends \OC\Files\Storage\Common {
return false;
}
if (!$this->file_exists($path)) {
$this->client->put($this->absPath($path), '');
$this->getConnection()->put($this->absPath($path), '');
} else {
return false;
}
@@ -241,11 +272,11 @@ class SFTP extends \OC\Files\Storage\Common {
}
public function getFile($path, $target) {
$this->client->get($path, $target);
$this->getConnection()->get($path, $target);
}
public function uploadFile($path, $target) {
$this->client->put($target, $path, NET_SFTP_LOCAL_FILE);
$this->getConnection()->put($target, $path, NET_SFTP_LOCAL_FILE);
}
public function rename($source, $target) {
@@ -253,7 +284,7 @@ class SFTP extends \OC\Files\Storage\Common {
if (!$this->is_dir($target) && $this->file_exists($target)) {
$this->unlink($target);
}
return $this->client->rename(
return $this->getConnection()->rename(
$this->absPath($source),
$this->absPath($target)
);
@@ -264,7 +295,7 @@ class SFTP extends \OC\Files\Storage\Common {
public function stat($path) {
try {
$stat = $this->client->stat($this->absPath($path));
$stat = $this->getConnection()->stat($this->absPath($path));
$mtime = $stat ? $stat['mtime'] : -1;
$size = $stat ? $stat['size'] : 0;
@@ -279,7 +310,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;
}
}

View File

@@ -47,6 +47,12 @@ class Swift extends \OC\Files\Storage\Common {
* @var string
*/
private $bucket;
/**
* Connection parameters
*
* @var array
*/
private $params;
/**
* @var array
*/
@@ -84,7 +90,7 @@ class Swift extends \OC\Files\Storage\Common {
*/
private function doesObjectExist($path) {
try {
$this->container->getPartialObject($path);
$this->getContainer()->getPartialObject($path);
return true;
} catch (ClientErrorResponseException $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
@@ -110,37 +116,7 @@ class Swift extends \OC\Files\Storage\Common {
$params['service_name'] = 'cloudFiles';
}
$settings = array(
'username' => $params['user'],
);
if (isset($params['password'])) {
$settings['password'] = $params['password'];
} else if (isset($params['key'])) {
$settings['apiKey'] = $params['key'];
}
if (isset($params['tenant'])) {
$settings['tenantName'] = $params['tenant'];
}
if (isset($params['timeout'])) {
$settings['timeout'] = $params['timeout'];
}
$this->anchor = new OpenStack($params['url'], $settings);
$this->connection = $this->anchor->objectStoreService($params['service_name'], $params['region']);
try {
$this->container = $this->connection->getContainer($this->bucket);
} catch (ClientErrorResponseException $e) {
$this->container = $this->connection->createContainer($this->bucket);
}
if (!$this->file_exists('.')) {
$this->mkdir('.');
}
$this->params = $params;
}
public function mkdir($path) {
@@ -158,7 +134,7 @@ class Swift extends \OC\Files\Storage\Common {
$customHeaders = array('content-type' => 'httpd/unix-directory');
$metadataHeaders = DataObject::stockHeaders(array());
$allHeaders = $customHeaders + $metadataHeaders;
$this->container->uploadObject($path, '', $allHeaders);
$this->getContainer()->uploadObject($path, '', $allHeaders);
} catch (Exceptions\CreateUpdateError $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
return false;
@@ -198,7 +174,7 @@ class Swift extends \OC\Files\Storage\Common {
}
try {
$this->container->dataObject()->setName($path . '/')->delete();
$this->getContainer()->dataObject()->setName($path . '/')->delete();
} catch (Exceptions\DeleteError $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
return false;
@@ -221,7 +197,7 @@ class Swift extends \OC\Files\Storage\Common {
try {
$files = array();
/** @var OpenCloud\Common\Collection $objects */
$objects = $this->container->objectList(array(
$objects = $this->getContainer()->objectList(array(
'prefix' => $path,
'delimiter' => '/'
));
@@ -251,7 +227,7 @@ class Swift extends \OC\Files\Storage\Common {
}
try {
$object = $this->container->getPartialObject($path);
$object = $this->getContainer()->getPartialObject($path);
} catch (ClientErrorResponseException $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
return false;
@@ -304,7 +280,7 @@ class Swift extends \OC\Files\Storage\Common {
}
try {
$this->container->dataObject()->setName($path)->delete();
$this->getContainer()->dataObject()->setName($path)->delete();
} catch (ClientErrorResponseException $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
return false;
@@ -322,7 +298,7 @@ class Swift extends \OC\Files\Storage\Common {
$tmpFile = \OC_Helper::tmpFile();
self::$tmpFiles[$tmpFile] = $path;
try {
$object = $this->container->getObject($path);
$object = $this->getContainer()->getObject($path);
} catch (ClientErrorResponseException $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
return false;
@@ -375,7 +351,7 @@ class Swift extends \OC\Files\Storage\Common {
if ($this->is_dir($path)) {
return 'httpd/unix-directory';
} else if ($this->file_exists($path)) {
$object = $this->container->getPartialObject($path);
$object = $this->getContainer()->getPartialObject($path);
return $object->getContentType();
}
return false;
@@ -392,14 +368,15 @@ class Swift extends \OC\Files\Storage\Common {
$path .= '/';
}
$object = $this->container->getPartialObject($path);
$object = $this->getContainer()->getPartialObject($path);
$object->saveMetadata($metadata);
return true;
} else {
$customHeaders = array('content-type' => 'text/plain');
$mimeType = \OC_Helper::getMimetypeDetector()->detectPath($path);
$customHeaders = array('content-type' => $mimeType);
$metadataHeaders = DataObject::stockHeaders($metadata);
$allHeaders = $customHeaders + $metadataHeaders;
$this->container->uploadObject($path, '', $allHeaders);
$this->getContainer()->uploadObject($path, '', $allHeaders);
return true;
}
}
@@ -415,7 +392,7 @@ class Swift extends \OC\Files\Storage\Common {
$this->unlink($path2);
try {
$source = $this->container->getPartialObject($path1);
$source = $this->getContainer()->getPartialObject($path1);
$source->copy($this->bucket.'/'.$path2);
} catch (ClientErrorResponseException $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
@@ -428,7 +405,7 @@ class Swift extends \OC\Files\Storage\Common {
$this->unlink($path2);
try {
$source = $this->container->getPartialObject($path1 . '/');
$source = $this->getContainer()->getPartialObject($path1 . '/');
$source->copy($this->bucket.'/'.$path2 . '/');
} catch (ClientErrorResponseException $e) {
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
@@ -486,16 +463,71 @@ class Swift extends \OC\Files\Storage\Common {
return $this->id;
}
/**
* Returns the connection
*
* @return OpenCloud\ObjectStore\Service connected client
* @throws \Exception if connection could not be made
*/
public function getConnection() {
if (!is_null($this->connection)) {
return $this->connection;
}
$settings = array(
'username' => $this->params['user'],
);
if (!empty($this->params['password'])) {
$settings['password'] = $this->params['password'];
} else if (!empty($this->params['key'])) {
$settings['apiKey'] = $this->params['key'];
}
if (!empty($this->params['tenant'])) {
$settings['tenantName'] = $this->params['tenant'];
}
if (!empty($this->params['timeout'])) {
$settings['timeout'] = $this->params['timeout'];
}
$this->anchor = new OpenStack($this->params['url'], $settings);
$this->connection = $this->anchor->objectStoreService($this->params['service_name'], $this->params['region']);
return $this->connection;
}
/**
* Returns the initialized object store container.
*
* @return OpenCloud\ObjectStore\Resource\Container
*/
public function getContainer() {
if (!is_null($this->container)) {
return $this->container;
}
try {
$this->container = $this->getConnection()->getContainer($this->bucket);
} catch (ClientErrorResponseException $e) {
$this->container = $this->getConnection()->createContainer($this->bucket);
}
if (!$this->file_exists('.')) {
$this->mkdir('.');
}
return $this->container;
}
public function writeBack($tmpFile) {
if (!isset(self::$tmpFiles[$tmpFile])) {
return false;
}
$fileData = fopen($tmpFile, 'r');
$this->container->uploadObject(self::$tmpFiles[$tmpFile], $fileData);
$this->getContainer()->uploadObject(self::$tmpFiles[$tmpFile], $fileData);
unlink($tmpFile);
}

0
apps/files_external/personal.php Executable file → Normal file
View File

View File

@@ -23,9 +23,10 @@
OC_Util::checkAdminUser();
OCP\Util::addScript('files_external', 'settings');
OCP\Util::addscript('3rdparty', 'chosen/chosen.jquery.min');
OCP\Util::addStyle('files_external', 'settings');
OCP\Util::addStyle('3rdparty', 'chosen/chosen');
OCP\Util::addScript('core', 'select2/select2');
OCP\Util::addStyle('core', 'select2/select2');
$backends = OC_Mount_Config::getBackends();
$personal_backends = array();
@@ -46,9 +47,6 @@ $tmpl->assign('isAdminPage', true);
$tmpl->assign('mounts', OC_Mount_Config::getSystemMountPoints());
$tmpl->assign('backends', $backends);
$tmpl->assign('personal_backends', $personal_backends);
$tmpl->assign('groups', OC_Group::getGroups());
$tmpl->assign('users', OCP\User::getUsers());
$tmpl->assign('userDisplayNames', OC_User::getDisplayNames());
$tmpl->assign('dependencies', OC_Mount_Config::checkDependencies());
$tmpl->assign('allowUserMounting', OCP\Config::getAppValue('files_external', 'allow_user_mounting', 'yes'));
return $tmpl->fetchPage();

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])): ?>
@@ -87,31 +88,8 @@
print_unescaped(json_encode($mount['applicable']['groups'])); ?>'
data-applicable-users='<?php if (isset($mount['applicable']['users']))
print_unescaped(json_encode($mount['applicable']['users'])); ?>'>
<select class="chzn-select"
multiple style="width:20em;"
data-placeholder="<?php p($l->t('No user or group')); ?>">
<option value="all"
<?php if (empty($mount['class']) || (isset($mount['applicable']['users']) && in_array('all', $mount['applicable']['users']))) print_unescaped('selected="selected"');?> >
<?php p($l->t('All Users')); ?>
</option>
<optgroup label="<?php p($l->t('Groups')); ?>">
<?php foreach ($_['groups'] as $group): ?>
<option value="<?php p($group); ?>(group)"
<?php if (isset($mount['applicable']['groups']) && in_array($group, $mount['applicable']['groups'])): ?>
selected="selected"
<?php endif; ?>><?php p($group); ?></option>
<?php endforeach; ?>
</optgroup>
<optgroup label="<?php p($l->t('Users')); ?>">
<?php foreach ($_['users'] as $user): ?>
<option value="<?php p($user); ?>"
<?php if (isset($mount['applicable']['users']) && in_array($user, $mount['applicable']['users'])): ?>
selected="selected"
<?php endif; ?>><?php p($_['userDisplayNames'][$user]); ?></option>
<?php endforeach; ?>
</optgroup>
</select>
</td>
<input type="hidden" class="applicableUsers" style="width:20em;" value=""/>
</td>
<?php endif; ?>
<td <?php if (isset($mount['mountpoint'])): ?>class="remove"
<?php else: ?>style="visibility:hidden;"
@@ -149,7 +127,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>

View File

@@ -38,29 +38,11 @@ class AmazonS3 extends Storage {
public function tearDown() {
if ($this->instance) {
$connection = $this->instance->getConnection();
try {
// NOTE(berendt): clearBucket() is not working with Ceph
$iterator = $connection->getIterator('ListObjects', array(
'Bucket' => $this->config['amazons3']['bucket']
));
foreach ($iterator as $object) {
$connection->deleteObject(array(
'Bucket' => $this->config['amazons3']['bucket'],
'Key' => $object['Key']
));
}
} catch (S3Exception $e) {
}
$connection->deleteBucket(array(
'Bucket' => $this->config['amazons3']['bucket']
));
//wait some seconds for completing the replication
sleep(30);
$this->instance->rmdir('');
}
}
public function testStat() {
$this->markTestSkipped('S3 doesn\'t update the parents folder mtime');
}
}

View File

@@ -0,0 +1,117 @@
<?php
/**
* ownCloud
*
* @author Jörn Friedrich Dreyer
* @copyright 2012 Jörn Friedrich Dreyer jfd@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 Test\Files\Storage;
class AmazonS3Migration extends \PHPUnit_Framework_TestCase {
/**
* @var \OC\Files\Storage\Storage instance
*/
protected $instance;
public function setUp () {
$uuid = uniqid();
$this->params['key'] = 'key'.$uuid;
$this->params['secret'] = 'secret'.$uuid;
$this->params['bucket'] = 'bucket'.$uuid;
$this->oldId = 'amazon::' . $this->params['key'] . md5($this->params['secret']);
$this->newId = 'amazon::' . $this->params['bucket'];
}
public function tearDown () {
$this->deleteStorage($this->oldId);
$this->deleteStorage($this->newId);
}
public function testUpdateLegacyOnlyId () {
// add storage ids
$oldCache = new \OC\Files\Cache\Cache($this->oldId);
// add file to old cache
$fileId = $oldCache->put('/', array('size' => 0, 'mtime' => time(), 'mimetype' => 'httpd/directory'));
try {
$this->instance = new \OC\Files\Storage\AmazonS3($this->params);
} catch (\Exception $e) {
//ignore
}
$storages = $this->getStorages();
$this->assertTrue(isset($storages[$this->newId]));
$this->assertFalse(isset($storages[$this->oldId]));
$this->assertSame((int)$oldCache->getNumericStorageId(), (int)$storages[$this->newId]);
list($storageId, $path) = \OC\Files\Cache\Cache::getById($fileId);
$this->assertSame($this->newId, $storageId);
$this->assertSame('/', $path);
}
public function testUpdateLegacyAndNewId () {
// add storage ids
$oldCache = new \OC\Files\Cache\Cache($this->oldId);
new \OC\Files\Cache\Cache($this->newId);
// add file to old cache
$fileId = $oldCache->put('/', array('size' => 0, 'mtime' => time(), 'mimetype' => 'httpd/directory'));
try {
$this->instance = new \OC\Files\Storage\AmazonS3($this->params);
} catch (\Exception $e) {
//ignore
}
$storages = $this->getStorages();
$this->assertTrue(isset($storages[$this->newId]));
$this->assertFalse(isset($storages[$this->oldId]));
$this->assertNull(\OC\Files\Cache\Cache::getById($fileId), 'old filecache has not been cleared');
}
/**
* @param $storages
* @return array
*/
public function getStorages() {
$storages = array();
$stmt = \OC::$server->getDatabaseConnection()->prepare(
'SELECT `numeric_id`, `id` FROM `*PREFIX*storages` WHERE `id` IN (?, ?)'
);
$stmt->execute(array($this->oldId, $this->newId));
while ($row = $stmt->fetch()) {
$storages[$row['id']] = $row['numeric_id'];
}
return $storages;
}
public function deleteStorage($id) {
$stmt = \OC::$server->getDatabaseConnection()->prepare(
'DELETE FROM `*PREFIX*storages` WHERE `id` = ?'
);
$stmt->execute(array($id));
}
}

View File

@@ -0,0 +1,328 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace Tests\Files_External;
use OC\Files\Filesystem;
use OC\User\User;
class EtagPropagator extends \PHPUnit_Framework_TestCase {
protected function getUser() {
return new User(uniqid(), null);
}
/**
* @return \PHPUnit_Framework_MockObject_MockObject | \OC\Files\Cache\ChangePropagator
*/
protected function getChangePropagator() {
return $this->getMockBuilder('\OC\Files\Cache\ChangePropagator')
->disableOriginalConstructor()
->getMock();
}
/**
* @return \PHPUnit_Framework_MockObject_MockObject | \OCP\IConfig
*/
protected function getConfig() {
$appConfig = array();
$userConfig = array();
$mock = $this->getMockBuilder('\OCP\IConfig')
->disableOriginalConstructor()
->getMock();
$mock->expects($this->any())
->method('getAppValue')
->will($this->returnCallback(function ($appId, $key, $default = null) use (&$appConfig) {
if (isset($appConfig[$appId]) and isset($appConfig[$appId][$key])) {
return $appConfig[$appId][$key];
} else {
return $default;
}
}));
$mock->expects($this->any())
->method('setAppValue')
->will($this->returnCallback(function ($appId, $key, $value) use (&$appConfig) {
if (!isset($appConfig[$appId])) {
$appConfig[$appId] = array();
}
$appConfig[$appId][$key] = $value;
}));
$mock->expects($this->any())
->method('getAppKeys')
->will($this->returnCallback(function ($appId) use (&$appConfig) {
if (!isset($appConfig[$appId])) {
$appConfig[$appId] = array();
}
return array_keys($appConfig[$appId]);
}));
$mock->expects($this->any())
->method('getUserValue')
->will($this->returnCallback(function ($userId, $appId, $key, $default = null) use (&$userConfig) {
if (isset($userConfig[$userId]) and isset($userConfig[$userId][$appId]) and isset($userConfig[$userId][$appId][$key])) {
return $userConfig[$userId][$appId][$key];
} else {
return $default;
}
}));
$mock->expects($this->any())
->method('setUserValue')
->will($this->returnCallback(function ($userId, $appId, $key, $value) use (&$userConfig) {
if (!isset($userConfig[$userId])) {
$userConfig[$userId] = array();
}
if (!isset($userConfig[$userId][$appId])) {
$userConfig[$userId][$appId] = array();
}
$userConfig[$userId][$appId][$key] = $value;
}));
return $mock;
}
public function testSingleUserMount() {
$time = time();
$user = $this->getUser();
$config = $this->getConfig();
$changePropagator = $this->getChangePropagator();
$propagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, $config);
$changePropagator->expects($this->once())
->method('addChange')
->with('/test');
$changePropagator->expects($this->once())
->method('propagateChanges')
->with($time);
$propagator->updateHook(array(
Filesystem::signal_param_path => '/test',
Filesystem::signal_param_mount_type => \OC_Mount_Config::MOUNT_TYPE_USER,
Filesystem::signal_param_users => $user->getUID(),
), $time);
}
public function testGlobalMountNoDirectUpdate() {
$time = time();
$user = $this->getUser();
$config = $this->getConfig();
$changePropagator = $this->getChangePropagator();
$propagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, $config);
// not updated directly
$changePropagator->expects($this->never())
->method('addChange');
$changePropagator->expects($this->never())
->method('propagateChanges');
$propagator->updateHook(array(
Filesystem::signal_param_path => '/test',
Filesystem::signal_param_mount_type => \OC_Mount_Config::MOUNT_TYPE_USER,
Filesystem::signal_param_users => 'all',
), $time);
// mount point marked as dirty
$this->assertEquals(array('/test'), $config->getAppKeys('files_external'));
$this->assertEquals($time, $config->getAppValue('files_external', '/test'));
}
public function testGroupMountNoDirectUpdate() {
$time = time();
$user = $this->getUser();
$config = $this->getConfig();
$changePropagator = $this->getChangePropagator();
$propagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, $config);
// not updated directly
$changePropagator->expects($this->never())
->method('addChange');
$changePropagator->expects($this->never())
->method('propagateChanges');
$propagator->updateHook(array(
Filesystem::signal_param_path => '/test',
Filesystem::signal_param_mount_type => \OC_Mount_Config::MOUNT_TYPE_GROUP,
Filesystem::signal_param_users => 'test',
), $time);
// mount point marked as dirty
$this->assertEquals(array('/test'), $config->getAppKeys('files_external'));
$this->assertEquals($time, $config->getAppValue('files_external', '/test'));
}
public function testGlobalMountNoDirtyMountPoint() {
$time = time();
$user = $this->getUser();
$config = $this->getConfig();
$changePropagator = $this->getChangePropagator();
$propagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, $config);
$changePropagator->expects($this->never())
->method('addChange');
$changePropagator->expects($this->never())
->method('propagateChanges');
$propagator->propagateDirtyMountPoints($time);
$this->assertEquals(0, $config->getUserValue($user->getUID(), 'files_external', '/test', 0));
}
public function testGlobalMountDirtyMountPointFirstTime() {
$time = time();
$user = $this->getUser();
$config = $this->getConfig();
$changePropagator = $this->getChangePropagator();
$propagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, $config);
$config->setAppValue('files_external', '/test', $time - 10);
$changePropagator->expects($this->once())
->method('addChange')
->with('/test');
$changePropagator->expects($this->once())
->method('propagateChanges')
->with($time);
$propagator->propagateDirtyMountPoints($time);
$this->assertEquals($time, $config->getUserValue($user->getUID(), 'files_external', '/test'));
}
public function testGlobalMountNonDirtyMountPoint() {
$time = time();
$user = $this->getUser();
$config = $this->getConfig();
$changePropagator = $this->getChangePropagator();
$propagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, $config);
$config->setAppValue('files_external', '/test', $time - 10);
$config->setUserValue($user->getUID(), 'files_external', '/test', $time - 10);
$changePropagator->expects($this->never())
->method('addChange');
$changePropagator->expects($this->never())
->method('propagateChanges');
$propagator->propagateDirtyMountPoints($time);
$this->assertEquals($time - 10, $config->getUserValue($user->getUID(), 'files_external', '/test'));
}
public function testGlobalMountNonDirtyMountPointOtherUser() {
$time = time();
$user = $this->getUser();
$user2 = $this->getUser();
$config = $this->getConfig();
$changePropagator = $this->getChangePropagator();
$propagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, $config);
$config->setAppValue('files_external', '/test', $time - 10);
$config->setUserValue($user2->getUID(), 'files_external', '/test', $time - 10);
$changePropagator->expects($this->once())
->method('addChange')
->with('/test');
$changePropagator->expects($this->once())
->method('propagateChanges')
->with($time);
$propagator->propagateDirtyMountPoints($time);
$this->assertEquals($time, $config->getUserValue($user->getUID(), 'files_external', '/test'));
}
public function testGlobalMountDirtyMountPointSecondTime() {
$time = time();
$user = $this->getUser();
$config = $this->getConfig();
$changePropagator = $this->getChangePropagator();
$propagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, $config);
$config->setAppValue('files_external', '/test', $time - 10);
$config->setUserValue($user->getUID(), 'files_external', '/test', $time - 20);
$changePropagator->expects($this->once())
->method('addChange')
->with('/test');
$changePropagator->expects($this->once())
->method('propagateChanges')
->with($time);
$propagator->propagateDirtyMountPoints($time);
$this->assertEquals($time, $config->getUserValue($user->getUID(), 'files_external', '/test'));
}
public function testGlobalMountMultipleUsers() {
$time = time();
$config = $this->getConfig();
$user1 = $this->getUser();
$user2 = $this->getUser();
$user3 = $this->getUser();
$changePropagator1 = $this->getChangePropagator();
$changePropagator2 = $this->getChangePropagator();
$changePropagator3 = $this->getChangePropagator();
$propagator1 = new \OCA\Files_External\EtagPropagator($user1, $changePropagator1, $config);
$propagator2 = new \OCA\Files_External\EtagPropagator($user2, $changePropagator2, $config);
$propagator3 = new \OCA\Files_External\EtagPropagator($user3, $changePropagator3, $config);
$config->setAppValue('files_external', '/test', $time - 10);
$changePropagator1->expects($this->once())
->method('addChange')
->with('/test');
$changePropagator1->expects($this->once())
->method('propagateChanges')
->with($time);
$propagator1->propagateDirtyMountPoints($time);
$this->assertEquals($time, $config->getUserValue($user1->getUID(), 'files_external', '/test'));
$this->assertEquals(0, $config->getUserValue($user2->getUID(), 'files_external', '/test', 0));
$this->assertEquals(0, $config->getUserValue($user3->getUID(), 'files_external', '/test', 0));
$changePropagator2->expects($this->once())
->method('addChange')
->with('/test');
$changePropagator2->expects($this->once())
->method('propagateChanges')
->with($time);
$propagator2->propagateDirtyMountPoints($time);
$this->assertEquals($time, $config->getUserValue($user1->getUID(), 'files_external', '/test'));
$this->assertEquals($time, $config->getUserValue($user2->getUID(), 'files_external', '/test', 0));
$this->assertEquals(0, $config->getUserValue($user3->getUID(), 'files_external', '/test', 0));
}
public function testGlobalMountMultipleDirtyMountPoints() {
$time = time();
$user = $this->getUser();
$config = $this->getConfig();
$changePropagator = $this->getChangePropagator();
$propagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, $config);
$config->setAppValue('files_external', '/test', $time - 10);
$config->setAppValue('files_external', '/foo', $time - 50);
$config->setAppValue('files_external', '/bar', $time - 70);
$config->setUserValue($user->getUID(), 'files_external', '/foo', $time - 70);
$config->setUserValue($user->getUID(), 'files_external', '/bar', $time - 70);
$changePropagator->expects($this->exactly(2))
->method('addChange');
$changePropagator->expects($this->once())
->method('propagateChanges')
->with($time);
$propagator->propagateDirtyMountPoints($time);
$this->assertEquals($time, $config->getUserValue($user->getUID(), 'files_external', '/test'));
$this->assertEquals($time, $config->getUserValue($user->getUID(), 'files_external', '/foo'));
$this->assertEquals($time - 70, $config->getUserValue($user->getUID(), 'files_external', '/bar'));
}
}

View File

@@ -20,14 +20,48 @@
*
*/
require_once __DIR__ . '/../../../lib/base.php';
class Test_Mount_Config_Dummy_Storage {
public function test() {
return true;
}
}
class Test_Mount_Config_Hook_Test {
static $signal;
static $params;
public static function setUpHooks() {
self::clear();
\OCP\Util::connectHook(
\OC\Files\Filesystem::CLASSNAME,
\OC\Files\Filesystem::signal_create_mount,
'\Test_Mount_Config_Hook_Test', 'createHookCallback');
\OCP\Util::connectHook(
\OC\Files\Filesystem::CLASSNAME,
\OC\Files\Filesystem::signal_delete_mount,
'\Test_Mount_Config_Hook_Test', 'deleteHookCallback');
}
public static function clear() {
self::$signal = null;
self::$params = null;
}
public static function createHookCallback($params) {
self::$signal = \OC\Files\Filesystem::signal_create_mount;
self::$params = $params;
}
public static function deleteHookCallback($params) {
self::$signal = \OC\Files\Filesystem::signal_delete_mount;
self::$params = $params;
}
public static function getLastCall() {
return array(self::$signal, self::$params);
}
}
/**
* Class Test_Mount_Config
*/
@@ -79,9 +113,11 @@ class Test_Mount_Config extends \PHPUnit_Framework_TestCase {
);
OC_Mount_Config::$skipTest = true;
Test_Mount_Config_Hook_Test::setupHooks();
}
public function tearDown() {
Test_Mount_Config_Hook_Test::clear();
OC_Mount_Config::$skipTest = false;
\OC_User::deleteUser(self::TEST_USER2);
@@ -329,6 +365,102 @@ class Test_Mount_Config extends \PHPUnit_Framework_TestCase {
$this->assertEquals(array_keys($options), array_keys($savedOptions));
}
public function testHooks() {
$mountPoint = '/test';
$mountType = 'user';
$applicable = 'all';
$isPersonal = false;
$mountConfig = array(
'host' => 'smbhost',
'user' => 'smbuser',
'password' => 'smbpassword',
'share' => 'smbshare',
'root' => 'smbroot'
);
// write config
$this->assertTrue(
OC_Mount_Config::addMountPoint(
$mountPoint,
'\OC\Files\Storage\SMB',
$mountConfig,
$mountType,
$applicable,
$isPersonal
)
);
list($hookName, $params) = Test_Mount_Config_Hook_Test::getLastCall();
$this->assertEquals(
\OC\Files\Filesystem::signal_create_mount,
$hookName
);
$this->assertEquals(
$mountPoint,
$params[\OC\Files\Filesystem::signal_param_path]
);
$this->assertEquals(
$mountType,
$params[\OC\Files\Filesystem::signal_param_mount_type]
);
$this->assertEquals(
$applicable,
$params[\OC\Files\Filesystem::signal_param_users]
);
Test_Mount_Config_Hook_Test::clear();
// edit
$mountConfig['host'] = 'anothersmbhost';
$this->assertTrue(
OC_Mount_Config::addMountPoint(
$mountPoint,
'\OC\Files\Storage\SMB',
$mountConfig,
$mountType,
$applicable,
$isPersonal
)
);
// hook must not be called on edit
list($hookName, $params) = Test_Mount_Config_Hook_Test::getLastCall();
$this->assertEquals(
null,
$hookName
);
Test_Mount_Config_Hook_Test::clear();
$this->assertTrue(
OC_Mount_Config::removeMountPoint(
$mountPoint,
$mountType,
$applicable,
$isPersonal
)
);
list($hookName, $params) = Test_Mount_Config_Hook_Test::getLastCall();
$this->assertEquals(
\OC\Files\Filesystem::signal_delete_mount,
$hookName
);
$this->assertEquals(
$mountPoint,
$params[\OC\Files\Filesystem::signal_param_path]
);
$this->assertEquals(
$mountType,
$params[\OC\Files\Filesystem::signal_param_mount_type]
);
$this->assertEquals(
$applicable,
$params[\OC\Files\Filesystem::signal_param_users]
);
}
/**
* Test password obfuscation
*/
@@ -800,4 +932,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']);
}
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace Test\Files\Storage;
class OwnCloudFunctions extends \PHPUnit_Framework_TestCase {
function configUrlProvider() {
return array(
array(
array(
'host' => 'testhost',
'root' => 'testroot',
'secure' => false
),
'http://testhost/remote.php/webdav/testroot/',
),
array(
array(
'host' => 'testhost',
'root' => 'testroot',
'secure' => true
),
'https://testhost/remote.php/webdav/testroot/',
),
array(
array(
'host' => 'http://testhost',
'root' => 'testroot',
'secure' => false
),
'http://testhost/remote.php/webdav/testroot/',
),
array(
array(
'host' => 'https://testhost',
'root' => 'testroot',
'secure' => false
),
'https://testhost/remote.php/webdav/testroot/',
),
array(
array(
'host' => 'https://testhost/testroot',
'root' => '',
'secure' => false
),
'https://testhost/testroot/remote.php/webdav/',
),
array(
array(
'host' => 'https://testhost/testroot',
'root' => 'subdir',
'secure' => false
),
'https://testhost/testroot/remote.php/webdav/subdir/',
),
array(
array(
'host' => 'http://testhost/testroot',
'root' => 'subdir',
'secure' => true
),
'http://testhost/testroot/remote.php/webdav/subdir/',
),
);
}
/**
* @dataProvider configUrlProvider
*/
public function testConfig($config, $expectedUri) {
$config['user'] = 'someuser';
$config['password'] = 'somepassword';
$instance = new \OC\Files\Storage\OwnCloud($config);
$this->assertEquals($expectedUri, $instance->createBaseUri());
}
}

View File

@@ -24,6 +24,12 @@ $owner = $_POST['owner'];
$name = $_POST['name'];
$password = $_POST['password'];
// Check for invalid name
if(!\OCP\Util::isValidFileName($name)) {
\OCP\JSON::error(array('data' => array('message' => $l->t('The mountpoint name contains invalid characters.'))));
exit();
}
$externalManager = new \OCA\Files_Sharing\External\Manager(
\OC::$server->getDatabaseConnection(),
\OC\Files\Filesystem::getMountManager(),

View File

@@ -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);

View File

@@ -12,6 +12,9 @@ OC::$CLASSPATH['OCA\Files\Share\Api'] = 'files_sharing/lib/api.php';
OC::$CLASSPATH['OCA\Files\Share\Maintainer'] = 'files_sharing/lib/maintainer.php';
OC::$CLASSPATH['OCA\Files\Share\Proxy'] = 'files_sharing/lib/proxy.php';
// Exceptions
OC::$CLASSPATH['OCA\Files_Sharing\Exceptions\BrokenPath'] = 'files_sharing/lib/exceptions.php';
\OCP\App::registerAdmin('files_sharing', 'settings-admin');
\OCA\Files_Sharing\Helper::registerHooks();
@@ -24,30 +27,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')
)
);
}
}

View File

@@ -2,7 +2,11 @@
<info>
<id>files_sharing</id>
<name>Share Files</name>
<description>File sharing between users</description>
<description>
This application enables users to share files within ownCloud. If enabled, the admin can choose which groups can share files. The applicable users can then share files and folders with other users and groups within ownCloud. In addition, if the admin enables the share link feature, an external link can be used to share files with other users outside of ownCloud. Admins can also enforce passwords, expirations dates, and enable server to server sharing via share links, as well as sharing from mobile devices.
Turning the feature off removes shared files and folders on the server for all share recipients, and also on the sync clients and mobile apps. More information is available in the ownCloud Documentation.
</description>
<licence>AGPL</licence>
<author>Michael Gapczynski, Bjoern Schiessle</author>
<requiremin>4.93</requiremin>
@@ -15,4 +19,5 @@
<files>public.php</files>
<webdav>publicwebdav.php</webdav>
</public>
<ocsid>166050</ocsid>
</info>

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');
}
}

View File

@@ -1 +1 @@
0.5.2
0.5.3

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;
}

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();

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('files_sharing', 'Add remote share'));
};
})();
$(document).ready(function () {

View File

@@ -56,6 +56,11 @@ OCA.Sharing.PublicApp = {
}
var mimetype = $('#mimetype').val();
var mimetypeIcon = $('#mimetypeIcon').val();
mimetypeIcon = mimetypeIcon.substring(0, mimetypeIcon.length - 3);
mimetypeIcon = mimetypeIcon + 'svg';
var previewSupported = $('#previewSupported').val();
if (typeof FileActions !== 'undefined') {
// Show file preview if previewer is available, images are already handled by the template
@@ -68,20 +73,25 @@ OCA.Sharing.PublicApp = {
}
}
// dynamically load image previews
if (mimetype.substr(0, mimetype.indexOf('/')) === 'image') {
var params = {
x: $(document).width() * window.devicePixelRatio,
y: $(document).height() * window.devicePixelRatio,
a: 'true',
file: encodeURIComponent(this.initialDir + $('#filename').val()),
t: $('#sharingToken').val(),
scalingup: 0
};
var params = {
x: $(document).width() * window.devicePixelRatio,
a: 'true',
file: encodeURIComponent(this.initialDir + $('#filename').val()),
t: $('#sharingToken').val(),
scalingup: 0
};
var img = $('<img class="publicpreview">');
var img = $('<img class="publicpreview">');
if (previewSupported === 'true' || mimetype.substr(0, mimetype.indexOf('/')) === 'image' && mimetype !== 'image/svg+xml') {
img.attr('src', OC.filePath('files_sharing', 'ajax', 'publicpreview.php') + '?' + OC.buildQueryString(params));
img.appendTo('#imgframe');
} else if (mimetype.substr(0, mimetype.indexOf('/')) !== 'video') {
img.attr('src', OC.Util.replaceSVGIcon(mimetypeIcon));
img.attr('width', 128);
img.appendTo('#imgframe');
}
if (this.fileList) {
@@ -163,7 +173,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();

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;

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);
}
});

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.",
@@ -30,6 +30,10 @@ $TRANSLATIONS = array(
"Direct link" => "Enllaz direutu",
"Remote Shares" => "Comparticiones remotes",
"Allow other instances to mount public links shared from this server" => "Permitir a otres instancies montar enllaces compartíos públicos d'esti sirvidor",
"Allow users to mount public link shares" => "Permitir a los usuarios montar enllaces compartíos públicos"
"Allow users to mount public link shares" => "Permitir a los usuarios montar enllaces compartíos públicos",
"Remote share" => "Compartición remota",
"Remote share password" => "Contraseña de compartición remota",
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Quies amestar compartición remota {name} de {owner}@{remote}?",
"Add remote share" => "Amestar compartición remota",
);
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";

View File

@@ -4,6 +4,12 @@ $TRANSLATIONS = array(
"Shared by" => "Споделено от",
"Name" => "Име",
"Save" => "Запис",
"Download" => "Изтегляне"
"Download" => "Изтегляне",
"Remote share" => "Прикачена Папка",
"Remote share password" => "Парола за прикачена папка",
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Желаеш ли да добавиш като прикачената папка {name} от {owner}@{remote}?",
"Couldn't add remote share" => "Неуспешно добавяне на отдалечена споделена директория.",
"Add remote share" => "Добави прикачена папка",
"Remote Shares" => "Прикачени Папки",
);
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";

View File

@@ -3,6 +3,9 @@ $TRANSLATIONS = array(
"Password" => "কূটশব্দ",
"Name" => "রাম",
"Save" => "সংরক্ষণ",
"Download" => "ডাউনলোড"
"Download" => "ডাউনলোড",
"Remote share" => "দুরবর্তী ভাগাভাগি",
"Couldn't add remote share" => "দুরবর্তী ভাগাভাগি যোগ করা গেলনা",
"Remote Shares" => "দুরবর্তী ভাগাভাগি",
);
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";

View File

@@ -31,6 +31,10 @@ $TRANSLATIONS = array(
"Direct link" => "Enllaç directe",
"Remote Shares" => "Compartició remota",
"Allow other instances to mount public links shared from this server" => "Permet que altres instàncies muntin enllaços públics compartits des d'aqeust servidor",
"Allow users to mount public link shares" => "Permet que usuaris muntin compartits amb enllaços públics"
"Allow users to mount public link shares" => "Permet que usuaris muntin compartits amb enllaços públics",
"Remote share" => "Compartició remota",
"Remote share password" => "Contrasenya de compartició remota",
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Voleu afegir la compartició remota {nom} des de {owner}@{remote}?",
"Add remote share" => "Afegeix compartició remota",
);
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";

View File

@@ -31,6 +31,10 @@ $TRANSLATIONS = array(
"Direct link" => "Přímý odkaz",
"Remote Shares" => "Vzdálená úložiště",
"Allow other instances to mount public links shared from this server" => "Povolit připojování veřejně sdílených odkazů z tohoto serveru",
"Allow users to mount public link shares" => "Povolit uživatelům připojovat veřejně sdílené odkazy"
"Allow users to mount public link shares" => "Povolit uživatelům připojovat veřejně sdílené odkazy",
"Remote share" => "Vzdálené úložiště",
"Remote share password" => "Heslo ke vzdálenému úložišti",
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Chcete přidat vzdálené úložiště {name} uživatele {owner}@{remote}?",
"Add remote share" => "Přidat vzdálené úložiště",
);
$PLURAL_FORMS = "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;";

View File

@@ -22,6 +22,12 @@ $TRANSLATIONS = array(
"Save" => "Gem",
"Download" => "Download",
"Download %s" => "Download %s",
"Direct link" => "Direkte link"
"Direct link" => "Direkte link",
"Remote share" => "Ekstern deling",
"Remote share password" => "Adgangskode for ekstern deling",
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Vil du tilføje den eksterne deling {name} fra {owner}@{remote}?",
"Couldn't add remote share" => "Kunne ikke tliføje den delte ekstern ressource",
"Add remote share" => "Tilføj ekstern deling",
"Remote Shares" => "Eksterne delinger",
);
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";

View File

@@ -31,6 +31,10 @@ $TRANSLATIONS = array(
"Direct link" => "Direkter Link",
"Remote Shares" => "Entfernte Freigaben",
"Allow other instances to mount public links shared from this server" => "Andere Instanzen zum Hinzufügen von öffentlichen Links, die über diesen Server Freigegeben werden, erlauben",
"Allow users to mount public link shares" => "Erlaube Nutzern das Hinzufügen von freigegebenen öffentlichen Links"
"Allow users to mount public link shares" => "Erlaube Nutzern das Hinzufügen von freigegebenen öffentlichen Links",
"Remote share" => "Entfernte Freigabe",
"Remote share password" => "Passwort für die entfernte Freigabe",
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Möchtest Du die entfernte Freigabe {name} von {owner}@{remote} hinzufügen?",
"Add remote share" => "Entfernte Freigabe hinzufügen",
);
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";

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