Compare commits

..

192 Commits

Author SHA1 Message Date
Sebastiaan Jansssen 7ee510ed38 Fix formatting 2018-02-26 16:28:12 +01:00
Sebastiaan Jansssen 136e9e66d2 Merge remote-tracking branch 'origin/dev-v7.8' into dev-v7.9 2018-02-26 16:26:54 +01:00
Shannon 55a2eb5b6c Merge branch 'dev-v7.9' of https://github.com/umbraco/Umbraco-CMS into dev-v7.9 2018-02-27 01:39:05 +11:00
Shannon 0c2c12fdd9 ensures that the template cache is cleared on startup if it hasn't been tracked in local storage 2018-02-27 01:38:53 +11:00
Sebastiaan Jansssen ec427afe14 Spelling 2018-02-26 15:19:45 +01:00
Shannon 155a304ce3 Allows GUID to be passed to ContentTreeControllerBase 2018-02-27 01:17:21 +11:00
Shannon 161a19a82b Adds cache busting for TinyMCE 2018-02-27 01:12:17 +11:00
Shannon e40ed5e858 not full caps for col name 2018-02-27 00:47:03 +11:00
Shannon 1ffbd734c8 Changes column and model for auditing to EventDateUtc 2018-02-27 00:46:09 +11:00
Shannon fd5bc49c87 Audits the member being exported, moves the export method to the service (again) but returns a proper model this time and raises an event, adds error checking to the export http request 2018-02-27 00:40:43 +11:00
Shannon Deminick 2301a00cd1 Merge pull request #2480 from umbraco/temp-U4-10794-2
Makes methods on IConsentService more explicit to allow mo
2018-02-26 22:43:26 +10:00
Shannon Deminick 313f7967cb Merge pull request #2479 from umbraco/temp-U4-10900-2
Fixes for U4-10900 Individual member data export functionality
2018-02-26 22:41:03 +10:00
Shannon 6adaa1f70c adds a notification when the member gets exported 2018-02-26 23:37:20 +11:00
Shannon 8960b0828e simplifies the exporter even more, no more reflection, no more streams 2018-02-26 23:23:25 +11:00
Shannon 397ab0e979 Merge branch 'dev-v7.9' into temp-U4-10900-2
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/views/member/export.html
2018-02-26 23:13:58 +11:00
Shannon 15c803b975 Changes how the json response is created 2018-02-26 23:12:33 +11:00
Shannon d62b36ae5b Merge branch 'temp-U4-10900-2' of https://github.com/umbraco/Umbraco-CMS into temp-U4-10900-2 2018-02-26 23:09:48 +11:00
Shannon 34506664df Moves the member export to happen on action click, adds a helper method to download a file via XHR 2018-02-26 23:09:29 +11:00
Sebastiaan Jansssen 2557b9a7fb Cleanup and makes a nice JSON object out of members + properties 2018-02-26 13:07:32 +01:00
Sebastiaan Jansssen ac66dc9892 Fix member groups and don't add a line at the top 2018-02-26 12:39:02 +01:00
Robert 11e3d8c761 Merge pull request #2474 from umbraco/temp-u4-10688
U4-10688 Obsolete property editors showing
2018-02-26 12:37:53 +01:00
Shannon Deminick 22b9941822 rename to match file naming conventions 2018-02-26 22:28:41 +11:00
Sebastiaan Jansssen b5b17a466b Cleanup and moving data to json format 2018-02-26 12:03:46 +01:00
Shannon 2c5b37a842 CHanges back to IConsentService but keeps method name changes 2018-02-26 21:56:00 +11:00
Niels Hartvig ae864a3b19 Mark old package installed Nested Content obsolete so it doesn't come up in the list 2018-02-26 11:32:58 +01:00
Mads Rasmussen 449e464a14 Merge branch 'mathiasrando-U4-10883' into dev-v7.9 2018-02-26 09:47:13 +01:00
Mads Rasmussen 5f72eb74db use attr to control button size + remove disabled state 2018-02-26 09:26:14 +01:00
Niels Hartvig fa85ce39c7 Changes filter of deprecated property editors to be on controller level instead of resolver 2018-02-26 09:22:54 +01:00
Shannon bdea519770 Changes from IConsentService to ILawfulDataProcessService to allow more flexibility with GDPR data processing in the future 2018-02-26 17:07:06 +11:00
Shannon 78657f34d6 I have updated the Consent.HistoryInternal to internal 2018-02-26 16:51:34 +11:00
Shannon e8fd7e2373 Fixes for U4-10900 Individual member data export functionality 2018-02-26 15:02:47 +11:00
Niels Hartvig 3da9fddd64 U4-10688 Obsolete property editors showing
Please be aware that if you do have obsolete property editors in use and set the setting "showDeprecatedPropertyEditors" to false you'll get an exception.
2018-02-24 21:31:10 +10:00
Sebastiaan Jansssen e1821697d4 Merge remote-tracking branch 'origin/dev-v7.8' into dev-v7.9
# Conflicts:
#	src/SolutionInfo.cs
#	src/Umbraco.Core/Configuration/UmbracoVersion.cs
2018-02-24 11:53:28 +01:00
Sebastiaan Jansssen a7756afae6 Bump version to 7.8.2 2018-02-24 11:50:39 +01:00
Sebastiaan Jansssen fcd3bb2eee Merge remote-tracking branch 'origin/dev-v7' into dev-v7.8
# Conflicts:
#	src/SolutionInfo.cs
#	src/Umbraco.Core/Configuration/UmbracoVersion.cs
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html
2018-02-24 11:48:20 +01:00
mikkelhm cf5dac0ddd fix xml in spanish lang file
(cherry picked from commit 2281cd8291)
2018-02-23 22:23:07 +01:00
Sebastiaan Janssen 1cdf5c042e Merge pull request #2473 from umbraco/temp-fix-spanish-language-xml
fix xml in spanish lang file
2018-02-23 22:19:10 +01:00
mikkelhm 2281cd8291 fix xml in spanish lang file 2018-02-23 22:02:47 +01:00
Robert fa68f7fafe Merge pull request #2445 from bjarnef/dev-v7-U4-10966
U4-10966 -  Single/Multiple Media Picker is messed up when dragging images
2018-02-23 15:15:36 +01:00
Mads Rasmussen 898e39dac5 Merge branch 'U4-10883' of git://github.com/mathiasrando/Umbraco-CMS into mathiasrando-U4-10883 2018-02-23 14:49:50 +01:00
Sebastiaan Jansssen f6045a49b5 Bump version to 7.7.13 2018-02-23 14:22:53 +01:00
Sebastiaan Jansssen 849cd4d315 Merge remote-tracking branch 'origin/dev-v7.8' into dev-v7.9 2018-02-23 13:49:57 +01:00
Sebastiaan Jansssen 10141bf4f6 Merge remote-tracking branch 'origin/dev-v7' into dev-v7.8
# Conflicts:
#	src/Umbraco.Web/Mvc/RenderRouteHandler.cs
2018-02-23 13:49:20 +01:00
Sebastiaan Janssen 11fcc8cd65 Merge pull request #2469 from umbraco/temp-U4-10900
Export member data functionality
2018-02-23 13:44:01 +01:00
Sebastiaan Jansssen 95aaf40281 Some more code cleanup and making sure we pass in the current user so that we can't avoid the sensitivedata check easily 2018-02-23 13:38:21 +01:00
Robert e25a39ec5d Merge pull request #2472 from umbraco/temp-U4-10964
Temp u4 10964
2018-02-23 13:33:47 +01:00
Sebastiaan Jansssen c165a34ed7 U4-10964 Adding a new property to a MemberType if you are not in Sensitive Data group throws a 404 when saving 2018-02-23 13:17:24 +01:00
Sebastiaan Janssen dc616bb6bf Merge pull request #2471 from alanmac/U4-11007
change newsletter subscribe to default for installation process - GDPR Compliance
2018-02-23 12:48:26 +01:00
Robert cd6c7e51fd Merge branch 'temp-U4-10900' of https://github.com/umbraco/Umbraco-CMS into temp-U4-10900 2018-02-23 12:24:37 +01:00
Alan Mac Kenna 2caad3f2d0 change newsletter subscribe to default for installation process 2018-02-23 11:24:15 +00:00
Robert 4850b01097 Added checks in place to stop users that has no access to sensitive data to export member data, removed some unused usings 2018-02-23 12:23:23 +01:00
Sebastiaan Jansssen 92c0cfb9df Additional action needs to be counted in the test 2018-02-23 10:58:33 +01:00
Sebastiaan Jansssen 8c5ee547b1 Does v3 MyGet API work? 2018-02-23 10:32:49 +01:00
Sebastiaan Jansssen b9c9a232cd Style updates, and removes unused usings 2018-02-23 10:26:11 +01:00
Sebastiaan Jansssen b565513052 Merge branch 'dev-v7.9' into temp-U4-10900 2018-02-23 09:46:06 +01:00
Robert cff24e6ccf Added translation keys for en.xml and deleted duplicate anchorInsert key in en_us.xml 2018-02-23 09:44:32 +01:00
Robert 8ef3ecd714 Forgot to commit the translation keys :| 2018-02-23 09:11:42 +01:00
Sebastiaan Janssen 1f66ebff1f Merge pull request #2470 from MMasey/dev-v7
U4-11005 Examine Search tools form does not submit when pressing Enter key
2018-02-22 18:49:17 +01:00
Sebastiaan Janssen 47f8b483a9 Merge pull request #2467 from christophnz/U4-5366
Uduf-Hackathon U4-5366
2018-02-22 18:46:27 +01:00
Sebastiaan Janssen 1c56cd48ae Merge pull request #2468 from emmanueltissera/U4-11002
Fixed email address in invite email [UDUF 2018 Hackathon]
2018-02-22 18:29:48 +01:00
Sebastiaan Janssen 6e9a9d6444 Merge pull request #2464 from lukaaaaaaaay/dev-v7.8
Hackathon: U4-10941 Name should be before Label when adding grid row configurations
2018-02-22 18:17:18 +01:00
Migaroez c4850199f7 U4-10999 Fixed wrong description on mediaresource.search
(cherry picked from commit af476e1536)
2018-02-22 17:36:39 +01:00
Sebastiaan Janssen 679333b9c9 Merge pull request #2456 from Migaroez/U4-10999
U4-10999 Fixed wrong description on mediaresource.search
2018-02-22 17:33:37 +01:00
Sebastiaan Janssen 02c609747d Merge pull request #2455 from GertyEngrie/dev-v7
Typo fix for nullreference in RenderRouteHandler
2018-02-22 17:32:25 +01:00
Sebastiaan Janssen 413e70db7d Merge pull request #2450 from richarth/patch-1
Grammar improvement
2018-02-22 17:31:29 +01:00
Sebastiaan Janssen c2cbac7ef9 Merge pull request #2448 from madsoulswe/temp-U4-10969
U4-10969 - Removed legacy file MediaUploader.ashx
2018-02-22 17:30:14 +01:00
Mike Masey a4a079cd1b Merge remote-tracking branch 'upstream/dev-v7' into dev-v7 2018-02-22 16:24:15 +00:00
Mike Masey 22fc9c617c Upate search button in examin search tools to use type="submit" 2018-02-22 16:18:59 +00:00
Sebastiaan Jansssen 0bcdfc8820 Merge branch 'skartknet-spanish_translations' into dev-v7 2018-02-22 17:11:38 +01:00
Sebastiaan Jansssen b868c06e79 Merge branch 'spanish_translations' of https://github.com/skartknet/Umbraco-CMS into skartknet-spanish_translations
# Conflicts:
#	src/Umbraco.Web.UI/umbraco/config/lang/es.xml
2018-02-22 17:11:09 +01:00
Robert f0dd32ea19 Removed export placeholder button and added "Action" button instead. 2018-02-22 16:52:16 +01:00
Robert 97607bc83b Added front and back end logic to export member data 2018-02-22 15:32:33 +01:00
ChristophNZ 5b41262f41 rewrote if/else to remove code duplication 2018-02-22 16:35:18 +10:00
Emmanuel Tissera e8e1cd8323 Fixed email address in invite email
http://issues.umbraco.org/issue/U4-11002 - Corrected issue where email address was not linked correctly in the invite template
2018-02-22 16:30:41 +10:00
ChristophNZ 1e17d6a527 continue loop after logging warning when null 2018-02-22 16:19:21 +10:00
ChristophNZ 0d05d0bf34 Import Document Type: use ContentTypeService to search for allowed child nodes if they are not part of the imported type(s) 2018-02-22 15:53:11 +10:00
Shannon 94184238a2 removes uneeded file 2018-02-22 13:19:42 +10:00
Shannon Deminick f21d1f879e Merge pull request #2459 from dinc5150/U4-10702
U4-10702 - Update Umb-modal icon to be consistent with other icons
2018-02-22 14:16:25 +11:00
Luke Phelan 9c1b2499d5 swapped the name and label fields in grid row editor. 2018-02-22 14:14:35 +11:00
Shannon Deminick 60a315f964 Merge pull request #2458 from cpevans/U4-10116
Check if user is admin to show propertyAlias regardless of debug status
2018-02-22 14:07:13 +11:00
Chris Evans 1870d5444d check for user admin group membership to show propertyAlias regardless of debug status
use the getCurrentUser() method of the userService to query the userGroups property of the current user
2018-02-22 13:54:50 +13:00
David Sheiles a927d5fb84 U4-10702 - Update Umb-modal icons to be consistant 2018-02-22 11:41:57 +11:00
Sebastiaan Jansssen e433daf5ff Bump version to 7.9.0 2018-02-21 16:06:59 +01:00
Migaroez af476e1536 U4-10999 Fixed wrong description on mediaresource.search 2018-02-21 11:02:56 +01:00
Gerty Engrie fcfb9afc22 typo fix 2018-02-20 14:24:51 +01:00
Claus 05818f6262 Merge branch 'temp-U4-10915' into dev-v7 2018-02-20 13:39:03 +01:00
Mads Rasmussen accf8ba5f2 Merge pull request #2444 from bjarnef/dev-v7-U4-10965
U4-10965 - Add translations of "Save and schedule"
2018-02-20 09:26:37 +01:00
Shannon 1788682af9 U4-10915 Umbraco automapper configuration is not valid 2018-02-20 19:16:53 +11:00
Sebastiaan Jansssen a41833c137 Merge remote-tracking branch 'origin/dev-v7.8' into dev-v7.9 2018-02-19 17:26:14 +01:00
Sebastiaan Jansssen 7abc6d3d4f Merge remote-tracking branch 'origin/dev-v7' into dev-v7.8 2018-02-19 17:20:12 +01:00
Sebastiaan Jansssen a551881ce2 TypedMember(object id) didn't know how to deal with UDI values coming in, breaking MNTP pickers 2018-02-19 17:13:03 +01:00
Sebastiaan Jansssen 4a5a8497b4 Merge remote-tracking branch 'origin/dev-v7.8' into dev-v7.9 2018-02-19 15:38:28 +01:00
Sebastiaan Jansssen 567c0977d0 Merge remote-tracking branch 'origin/dev-v7' into dev-v7.8
# Conflicts:
#	src/SolutionInfo.cs
#	src/Umbraco.Core/Configuration/UmbracoVersion.cs
2018-02-19 15:37:50 +01:00
Sebastiaan Jansssen 9ec4247350 Sync en and en_us - normalize whitespace 2018-02-19 15:24:50 +01:00
Sebastiaan Jansssen 2f31175db5 Bump version to 7.7.12 2018-02-19 14:20:47 +01:00
Richard Thompson c2722d8120 Grammar improvement
Improved the grammar of an error message
2018-02-16 15:19:16 +00:00
Mikulas Tomanka 325cefa349 Merge pull request #2426 from JeffreyPerplex/U4-9991-KeyboardShortcuts
http://issues.umbraco.org/issue/U4-9991 - Adding extra shortcuts
2018-02-16 10:36:24 +01:00
Mikulas Tomanka e43ecfd42c Merge branch 'dev-v7.9' into U4-9991-KeyboardShortcuts 2018-02-16 10:35:41 +01:00
Dennis Öhman 76725aa0d8 U4-10969 - Removed legacy file MediaUploader.ashx 2018-02-15 16:12:49 +01:00
Sebastiaan Janssen d4cf4cd700 Merge pull request #2432 from umbraco/temp-u4-10795
U4-10795
2018-02-15 16:07:22 +01:00
Sebastiaan Jansssen 48c0b95707 Only do a length check if performingIp not null 2018-02-15 16:03:12 +01:00
Sebastiaan Jansssen a5caf361e0 Update DateTime.Now usages to be UtcNow to be independent of local datetime 2018-02-15 15:55:40 +01:00
Sebastiaan Jansssen 74c3aaade5 Merge branch 'dev-v7.9' into temp-u4-10795 2018-02-15 15:50:15 +01:00
Stephan cf94da2b8f refactor user and group logging 2018-02-15 14:01:04 +01:00
Sebastiaan Jansssen 826d6d5b15 Merge remote-tracking branch 'origin/dev-v7.8' into dev-v7.9 2018-02-14 12:15:56 +01:00
Sebastiaan Jansssen 56f4904fc0 Merge remote-tracking branch 'origin/dev-v7' into dev-v7.8 2018-02-14 12:15:18 +01:00
Sebastiaan Jansssen 9178e7afcf Whoops, no need to introduce a new method here, old method was still available 2018-02-14 12:13:01 +01:00
Stephan 7f3733d4b5 cleanup 2018-02-14 11:27:43 +01:00
Sebastiaan Jansssen 3df23c3a34 Merge remote-tracking branch 'origin/dev-v7.8' into dev-v7.9
# Conflicts:
#	src/SolutionInfo.cs
#	src/Umbraco.Core/Configuration/UmbracoVersion.cs
2018-02-14 10:34:26 +01:00
Sebastiaan Jansssen 7746c4a014 Merge remote-tracking branch 'origin/dev-v7' into dev-v7.8
# Conflicts:
#	src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerPropertyConverter.cs
2018-02-14 10:28:51 +01:00
Sebastiaan Janssen b18945e630 Merge pull request #2447 from umbraco/temp-Fix-CDF-Version-Change
Updates the CDF version changer to to use a hash of the current versi…
2018-02-14 10:25:27 +01:00
Sebastiaan Jansssen 4640cf56d1 Make change non-breaking 2018-02-14 10:23:58 +01:00
Shannon eae1ce7012 fixes build 2018-02-14 12:05:05 +11:00
Shannon e900711180 Updates the CDF version changer to to use a hash of the current version and the date time so that it's not constantly updated if it's not changed. 2018-02-14 11:53:49 +11:00
Shannon 9f4875c8a7 removes SaveUserGroupWithUsersEventArgs and replaces with SaveEventArgs<UserGroupWithUsers> and changes the SavedUserGroupWithUsers to SavingUserGroup2 and SavedUserGroup2 2018-02-14 11:13:24 +11:00
Stephan 46902d4335 Merge branch 'temp-u4-10795' of https://github.com/umbraco/Umbraco-CMS into temp-u4-10795 2018-02-13 15:35:57 +01:00
Stephan 5eb22b0fd9 report added/removed users from groups 2018-02-13 15:30:58 +01:00
Stephan 6a18965946 Don't throw, DRY 2018-02-13 12:00:14 +01:00
Stephan be1ec05072 Use constants for field lengths 2018-02-13 11:32:21 +01:00
Stephan bb2caf85cb fix namespace 2018-02-13 09:47:23 +01:00
Shannon 8ee8bda953 fixes tests 2018-02-13 19:46:34 +11:00
Shannon b42f75df17 fixes tests 2018-02-13 19:29:19 +11:00
Shannon caa3a3319f Fixes issue with user groups being dirty and resetting dirty events 2018-02-13 18:54:13 +11:00
Shannon 5caf95331a Fixes PerformingUser for login/logout and actually logs the affected user 2018-02-13 18:29:39 +11:00
Bjarne Fyrstenborg 040e45ca70 Ensure move cursor is added the multiple media picker (sortable), but not on imagepicker prevalue editor, which not has ui-sortable 2018-02-12 22:27:23 +01:00
Bjarne Fyrstenborg bd3d626147 Add move cursor when media picker is multiple picker and thumbnails are sortable 2018-02-12 21:58:39 +01:00
Bjarne Fyrstenborg 47d08e69a4 Update comment 2018-02-12 21:56:48 +01:00
Bjarne Fyrstenborg df182674b4 Make thumbnail same size as add button 2018-02-12 21:37:22 +01:00
Bjarne Fyrstenborg 6ec9a2ae45 Add specific class for "add" list item 2018-02-12 21:01:08 +01:00
Bjarne Fyrstenborg bf00f7a663 Enable sortable option based on single or multiple media picker 2018-02-12 20:54:49 +01:00
Bjarne Fyrstenborg 74d8341620 Add bottom margin for multiple media picker 2018-02-12 20:46:48 +01:00
Bjarne Fyrstenborg 70207e5374 Fix issues with "add"-button in single and multiple media picker 2018-02-12 20:46:02 +01:00
Bjarne Fyrstenborg f4c038c64c Add translations of "Save and publish". 2018-02-12 18:04:46 +01:00
Shannon c4fa3aa649 Fixes issue with AffectedUser vs PerformingUser, updates the log formatting and uses the affectedDetails better, fixes the double event calling for auth stuff, fixes the change password event raising, fixes reporting the changed property values for members 2018-02-13 00:15:04 +11:00
Mikulas Tomanka d51cda7843 Merge pull request #2440 from ProNotion/patch-1
Fixed typo on user section of tour
2018-02-12 13:32:10 +01:00
Claus 372ffee42b Merge pull request #2437 from umbraco/temp-UAASSCRUM-1365
allowing to disable the template check when sending content through t…
2018-02-12 11:15:26 +01:00
Claus c1cfea7ebd removing constructor. 2018-02-12 11:12:46 +01:00
Shannon 3cdd8a0c43 removes methods from the interface we don't want to support 2018-02-12 20:48:42 +11:00
Shannon bf130584a6 updates docs 2018-02-12 18:28:51 +11:00
Shannon c3e67705ba updated some naming conventions for eventType 2018-02-12 18:24:07 +11:00
Shannon 7a80d689aa Fixes invalid login attempts being logged against the admin user, updates the audit handler to log unique keys for the eventType 2018-02-12 18:12:16 +11:00
Shannon eabc009f0c Adds validation to IAuditService.Write 2018-02-12 17:37:52 +11:00
ProNotion d3a2d88347 Fixed type on user section 2018-02-09 17:33:11 +00:00
Claus 93f4d65227 Merge pull request #2438 from umbraco/temp-U4-10958
Fixes broken MNTP by switching convert cases to the correct DataType
2018-02-09 14:42:08 +01:00
Robert a4c0342bda Fixes broken MNTP by switching convert cases to the correct DataType 2018-02-09 14:09:03 +01:00
Sebastiaan Janssen 6c328d729d Merge pull request #2435 from madsoulswe/temp-U4-10953
U4-10953- Help-drawer, (caption + h3 -> h5
2018-02-09 13:32:32 +01:00
Sebastiaan Jansssen 095633ba27 Test names were inverted 2018-02-09 13:27:25 +01:00
Claus 633d1bb0ae changing the template check to an enabled check instead. 2018-02-09 13:19:56 +01:00
Claus fb89c3da3c allowing to disable the template check when sending content through to render. 2018-02-09 12:58:50 +01:00
Stephan 29690df567 U4-10795 - misc fixes 2018-02-09 09:33:24 +01:00
Dennis Öhman 750600b202 U4-10953
Populate Caption in dashboardhelper
h2 -> h5
2018-02-08 15:37:50 +01:00
Stephan 61ce533a08 U4-10795 - better performing user management, fix upgrades 2018-02-08 10:16:53 +01:00
Stephan 0a44ad3ab6 U4-10795 - granular logging - roles 2018-02-07 16:50:49 +01:00
Stephan 7c34255abe Merge branch origin/dev-v7.9 into temp-u4-10795 2018-02-07 15:55:41 +01:00
Stephan d52216103b U4-10795 - granular logging 2018-02-07 15:51:18 +01:00
Stephan a26cd625e7 U4-10795 - granular logging 2018-02-07 15:38:50 +01:00
Mario Lopez 1f22191bbc fixed formatting 2018-02-07 11:35:39 +11:00
Mario Lopez 25088042a9 spanish translations 2018-02-07 11:28:11 +11:00
Stephan 02397fd90c U4-10795 - granular logging 2018-02-06 19:07:54 +01:00
Mathias Rando Juul e0ded863cc Changes revalidation of username and password.
Ensures that both inputs are revalidated on change(if the form is invalid) to avoid cases where changing your password doesn't reset $invalid and therefor doesn't allow you to resubmit the form without triggering a change in the username.
2018-02-06 06:35:41 +01:00
Mathias Rando Juul bd55594be1 Changes submit button to use umb-button directive. 2018-02-06 06:27:50 +01:00
Mathias Rando Juul 0e82696082 Updates formatting to match guidelines.
The guidelines for core contribution states that 4 spaces should be used for everything except XML.
2018-02-06 06:00:46 +01:00
Jeffrey - Perplex a7a6854f9b Adding extra shortcuts 2018-02-05 16:45:20 +01:00
Sebastiaan Janssen 23d225c14f Merge pull request #2424 from umbraco/temp-u4-10794
Temp u4 10794
2018-02-01 13:32:32 +01:00
Sebastiaan Jansssen 1a289be999 Whoops, I see what you did there, Consent can be internal indeed, cool! 2018-02-01 13:09:06 +01:00
Sebastiaan Jansssen 8a0485e093 Consent class really needs to be public 2018-02-01 13:07:02 +01:00
Sebastiaan Jansssen 647492fbfb Merge branch 'dev-v7.9' into temp-u4-10794 2018-02-01 12:55:53 +01:00
Sebastiaan Jansssen fc43d3cdee Merge branch 'dev-v7.8' into dev-v7.9 2018-01-31 17:03:19 +01:00
Stephan 971d7cf933 U4-10794 - refactor 2018-01-31 15:20:03 +01:00
Sebastiaan Jansssen 118a83e56c Consent class should be public for 3rd parties to be able to register consents 2018-01-31 13:19:30 +01:00
Sebastiaan Jansssen 1f6dea7177 Adds migration to create constent table 2018-01-31 12:47:41 +01:00
Sebastiaan Jansssen f2667c3360 Bumps version to 7.9.0 2018-01-31 12:47:16 +01:00
Sebastiaan Janssen f331736384 Merge pull request #2421 from umbraco/temp-U4-10797
U4-10797 Sensitive values
2018-01-31 10:59:18 +01:00
Shannon a6bc94185c Ensures that a user without access to sensitive values cannot change a member type's sensitive values, this also validates on the server side 2018-01-30 19:38:42 -06:00
Shannon 58533ca17e Validates sensitive properties during model binding to ensure that someone cannot hack a posted value for sensitive props if they don't have access to sensitive props. 2018-01-29 13:24:27 -06:00
Shannon 427e99dd29 WIP - need to validate sensitive properties for invalid posted data 2018-01-29 12:20:06 -06:00
Shannon 1ec09e15cf fixes tests 2018-01-29 12:02:20 -06:00
Shannon a48568d1ba Fixes problem where the sensitive value placeholder was being posted back to the server for saving, instead we don't post this value at all which means it will not be overwritten when it posts back. 2018-01-29 11:38:08 -06:00
Stephan 5aa1b81cd4 U4-10794 - revert PEtaPocoMapper changes 2018-01-29 16:27:16 +01:00
Stephan e1208e5019 U4-10794 - use string ids, not udis 2018-01-29 11:30:01 +01:00
Shannon 995f644501 Adds upgrade logic, fixes tests, fixes model mappings to not do so much AfterMap 2018-01-26 17:27:35 -07:00
Shannon f7eec8dafb Allows editing the property metadata even if the property type is locked 2018-01-26 16:24:40 -07:00
Shannon e039265fab adds descriptions to the member type options 2018-01-26 16:11:14 -07:00
Shannon f08192a4ab Changes copy for hidden values and makes the rendering for hidden values it's own property editor view so we can more easily update it. 2018-01-26 15:52:12 -07:00
Stephan a16ac1f0ba U4-10794 - implement basic consent service 2018-01-26 10:57:12 +01:00
Shannon 469d6f9ca3 Now checks for the sensitiveData group to view sensitive data 2018-01-25 15:06:23 -07:00
Shannon 54cc80f4e8 Updates all required mappings to use AutoMapperExtensions.MapWithUmbracoContext where appropriate 2018-01-25 14:41:28 -07:00
Shannon 64628c0a9d Gets the IsSensitive property mapped to the display object and changes the property output to show that the field contains sensitive data. Refactors the TabsAndPropertiesResolver and simplifies it a lot which also allows us to pass in an UmbracoContext which we'll need in order to determine a user's rights, etc... Refactors how the TreeNodeUrls are mapped and they now use a proper mapper. This now means we do much less in AfterMap 2018-01-25 14:14:12 -07:00
Mario Lopez 1e8bdc8f0c no message 2018-01-25 10:18:06 +11:00
Shannon b49db0e941 Initial commit for member type sensitive data, this adds the db and model requirements and updates the normal property editor for a member type which saves the isSensitive data to the db. 2018-01-23 13:01:31 -07:00
Mario Lopez 5678be0db9 Merge remote-tracking branch 'Umbraco/dev-v7' into dev-v7 2017-11-10 09:37:00 +11:00
Mario Lopez 464a227af1 Merge pull request #1 from umbraco/dev-v7
Update from  original
2017-10-08 15:45:13 +11:00
174 changed files with 6506 additions and 2625 deletions
+1 -1
View File
@@ -2,6 +2,6 @@
<configuration>
<packageSources>
<add key="nuget.org" value="https://www.nuget.org/api/v2/" />
<add key="umbracocore" value="http://www.myget.org/f/umbracocore/" />
<add key="umbracocore" value="https://www.myget.org/F/umbracocore/api/v3/index.json" />
</packageSources>
</configuration>
+2 -2
View File
@@ -11,5 +11,5 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("7.8.2")]
[assembly: AssemblyInformationalVersion("7.8.2")]
[assembly: AssemblyFileVersion("7.9.0")]
[assembly: AssemblyInformationalVersion("7.9.0")]
@@ -0,0 +1,351 @@
using System;
using System.Linq;
using System.Text;
using System.Threading;
using System.Web;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
namespace Umbraco.Core.Auditing
{
public sealed class AuditEventHandler : ApplicationEventHandler
{
private IAuditService _auditServiceInstance;
private IUserService _userServiceInstance;
private IEntityService _entityServiceInstance;
private IUser CurrentPerformingUser
{
get
{
var identity = Thread.CurrentPrincipal?.GetUmbracoIdentity();
return identity == null
? new User { Id = 0, Name = "SYSTEM", Email = "" }
: _userServiceInstance.GetUserById(Convert.ToInt32(identity.Id));
}
}
private IUser GetPerformingUser(int userId)
{
var found = userId >= 0 ? _userServiceInstance.GetUserById(userId) : null;
return found ?? new User {Id = 0, Name = "SYSTEM", Email = ""};
}
private string PerformingIp
{
get
{
var httpContext = HttpContext.Current == null ? (HttpContextBase) null : new HttpContextWrapper(HttpContext.Current);
var ip = httpContext.GetCurrentRequestIpAddress();
if (ip.ToLowerInvariant().StartsWith("unknown")) ip = "";
return ip;
}
}
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
_auditServiceInstance = applicationContext.Services.AuditService;
_userServiceInstance = applicationContext.Services.UserService;
_entityServiceInstance = applicationContext.Services.EntityService;
//BackOfficeUserManager.AccountLocked += ;
//BackOfficeUserManager.AccountUnlocked += ;
BackOfficeUserManager.ForgotPasswordRequested += OnForgotPasswordRequest;
BackOfficeUserManager.ForgotPasswordChangedSuccess += OnForgotPasswordChange;
BackOfficeUserManager.LoginFailed += OnLoginFailed;
//BackOfficeUserManager.LoginRequiresVerification += ;
BackOfficeUserManager.LoginSuccess += OnLoginSuccess;
BackOfficeUserManager.LogoutSuccess += OnLogoutSuccess;
BackOfficeUserManager.PasswordChanged += OnPasswordChanged;
BackOfficeUserManager.PasswordReset += OnPasswordReset;
//BackOfficeUserManager.ResetAccessFailedCount += ;
UserService.SavedUserGroup2 += OnSavedUserGroupWithUsers;
UserService.SavedUser += OnSavedUser;
UserService.DeletedUser += OnDeletedUser;
UserService.UserGroupPermissionsAssigned += UserGroupPermissionAssigned;
MemberService.Saved += OnSavedMember;
MemberService.Deleted += OnDeletedMember;
MemberService.AssignedRoles += OnAssignedRoles;
MemberService.RemovedRoles += OnRemovedRoles;
MemberService.Exported += OnMemberExported;
}
private string FormatEmail(IMember member)
{
return member == null ? string.Empty : member.Email.IsNullOrWhiteSpace() ? "" : $"<{member.Email}>";
}
private string FormatEmail(IUser user)
{
return user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>";
}
private void OnRemovedRoles(IMemberService sender, RolesEventArgs args)
{
var performingUser = CurrentPerformingUser;
var roles = string.Join(", ", args.Roles);
var members = sender.GetAllMembers(args.MemberIds).ToDictionary(x => x.Id, x => x);
foreach (var id in args.MemberIds)
{
members.TryGetValue(id, out var member);
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
-1, $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}",
"umbraco/member/roles/removed", $"roles modified, removed {roles}");
}
}
private void OnAssignedRoles(IMemberService sender, RolesEventArgs args)
{
var performingUser = CurrentPerformingUser;
var roles = string.Join(", ", args.Roles);
var members = sender.GetAllMembers(args.MemberIds).ToDictionary(x => x.Id, x => x);
foreach (var id in args.MemberIds)
{
members.TryGetValue(id, out var member);
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
-1, $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}",
"umbraco/member/roles/assigned", $"roles modified, assigned {roles}");
}
}
private void OnMemberExported(IMemberService sender, ExportedMemberEventArgs exportedMemberEventArgs)
{
var performingUser = CurrentPerformingUser;
var member = exportedMemberEventArgs.Member;
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
-1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}",
"umbraco/member/exported", "exported member data");
}
private void OnSavedUserGroupWithUsers(IUserService sender, SaveEventArgs<UserGroupWithUsers> saveEventArgs)
{
var performingUser = CurrentPerformingUser;
foreach (var groupWithUser in saveEventArgs.SavedEntities)
{
var group = groupWithUser.UserGroup;
var dp = string.Join(", ", ((UserGroup)group).GetPreviouslyDirtyProperties());
var sections = ((UserGroup)group).WasPropertyDirty("AllowedSections")
? string.Join(", ", group.AllowedSections)
: null;
var perms = ((UserGroup)group).WasPropertyDirty("Permissions")
? string.Join(", ", group.Permissions)
: null;
var sb = new StringBuilder();
sb.Append($"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)};");
if (sections != null)
sb.Append($", assigned sections: {sections}");
if (perms != null)
{
if (sections != null)
sb.Append(", ");
sb.Append($"default perms: {perms}");
}
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
-1, $"User Group {group.Id} \"{group.Name}\" ({group.Alias})",
"umbraco/user-group/save", $"{sb}");
// now audit the users that have changed
foreach (var user in groupWithUser.RemovedUsers)
{
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
user.Id, $"User \"{user.Name}\" {FormatEmail(user)}",
"umbraco/user-group/save", $"Removed user \"{user.Name}\" {FormatEmail(user)} from group {group.Id} \"{group.Name}\" ({group.Alias})");
}
foreach (var user in groupWithUser.AddedUsers)
{
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
user.Id, $"User \"{user.Name}\" {FormatEmail(user)}",
"umbraco/user-group/save", $"Added user \"{user.Name}\" {FormatEmail(user)} to group {group.Id} \"{group.Name}\" ({group.Alias})");
}
}
}
private void UserGroupPermissionAssigned(IUserService sender, SaveEventArgs<EntityPermission> saveEventArgs)
{
var performingUser = CurrentPerformingUser;
var perms = saveEventArgs.SavedEntities;
foreach (var perm in perms)
{
var group = sender.GetUserGroupById(perm.UserGroupId);
var assigned = string.Join(", ", perm.AssignedPermissions);
var entity = _entityServiceInstance.Get(perm.EntityId);
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
-1, $"User Group {group.Id} \"{group.Name}\" ({group.Alias})",
"umbraco/user-group/permissions-change", $"assigning {(string.IsNullOrWhiteSpace(assigned) ? "(nothing)" : assigned)} on id:{perm.EntityId} \"{entity.Name}\"");
}
}
private void OnSavedMember(IMemberService sender, SaveEventArgs<IMember> saveEventArgs)
{
var performingUser = CurrentPerformingUser;
var members = saveEventArgs.SavedEntities;
foreach (var member in members)
{
var dp = string.Join(", ", ((Member) member).GetPreviouslyDirtyProperties());
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
-1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}",
"umbraco/member/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}");
}
}
private void OnDeletedMember(IMemberService sender, DeleteEventArgs<IMember> deleteEventArgs)
{
var performingUser = CurrentPerformingUser;
var members = deleteEventArgs.DeletedEntities;
foreach (var member in members)
{
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
-1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}",
"umbraco/member/delete", $"delete member id:{member.Id} \"{member.Name}\" {FormatEmail(member)}");
}
}
private void OnSavedUser(IUserService sender, SaveEventArgs<IUser> saveEventArgs)
{
var performingUser = CurrentPerformingUser;
var affectedUsers = saveEventArgs.SavedEntities;
foreach (var affectedUser in affectedUsers)
{
var groups = affectedUser.WasPropertyDirty("Groups")
? string.Join(", ", affectedUser.Groups.Select(x => x.Alias))
: null;
var dp = string.Join(", ", ((User)affectedUser).GetPreviouslyDirtyProperties());
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}",
"umbraco/user/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}{(groups == null ? "" : "; groups assigned: " + groups)}");
}
}
private void OnDeletedUser(IUserService sender, DeleteEventArgs<IUser> deleteEventArgs)
{
var performingUser = CurrentPerformingUser;
var affectedUsers = deleteEventArgs.DeletedEntities;
foreach (var affectedUser in affectedUsers)
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}",
"umbraco/user/delete", "delete user");
}
private void OnLoginSuccess(object sender, EventArgs args)
{
if (args is IdentityAuditEventArgs identityArgs)
{
var performingUser = GetPerformingUser(identityArgs.PerformingUser);
WriteAudit(performingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/sign-in/login", "login success");
}
}
private void OnLogoutSuccess(object sender, EventArgs args)
{
if (args is IdentityAuditEventArgs identityArgs)
{
var performingUser = GetPerformingUser(identityArgs.PerformingUser);
WriteAudit(performingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/sign-in/logout", "logout success");
}
}
private void OnPasswordReset(object sender, EventArgs args)
{
if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0)
{
WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/reset", "password reset");
}
}
private void OnPasswordChanged(object sender, EventArgs args)
{
if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0)
{
WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/change", "password change");
}
}
private void OnLoginFailed(object sender, EventArgs args)
{
if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0)
{
WriteAudit(identityArgs.PerformingUser, 0, identityArgs.IpAddress, "umbraco/user/sign-in/failed", "login failed", affectedDetails: "");
}
}
private void OnForgotPasswordChange(object sender, EventArgs args)
{
if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0)
{
WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/forgot/change", "password forgot/change");
}
}
private void OnForgotPasswordRequest(object sender, EventArgs args)
{
if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0)
{
WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/forgot/request", "password forgot/request");
}
}
private void WriteAudit(int performingId, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null)
{
var performingUser = _userServiceInstance.GetUserById(performingId);
var performingDetails = performingUser == null
? $"User UNKNOWN:{performingId}"
: $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}";
WriteAudit(performingId, performingDetails, affectedId, ipAddress, eventType, eventDetails, affectedDetails);
}
private void WriteAudit(IUser performingUser, int affectedId, string ipAddress, string eventType, string eventDetails)
{
var performingDetails = performingUser == null
? $"User UNKNOWN"
: $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}";
WriteAudit(performingUser?.Id ?? 0, performingDetails, affectedId, ipAddress, eventType, eventDetails);
}
private void WriteAudit(int performingId, string performingDetails, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null)
{
if (affectedDetails == null)
{
var affectedUser = _userServiceInstance.GetUserById(affectedId);
affectedDetails = affectedUser == null
? $"User UNKNOWN:{affectedId}"
: $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}";
}
_auditServiceInstance.Write(performingId, performingDetails,
ipAddress,
DateTime.UtcNow,
affectedId, affectedDetails,
eventType, eventDetails);
}
}
}
@@ -1,4 +1,5 @@
using System;
using System.ComponentModel;
using System.Threading;
using System.Web;
using Umbraco.Core.Security;
@@ -45,12 +46,8 @@ namespace Umbraco.Core.Auditing
/// </summary>
public string Username { get; private set; }
/// <summary>
/// Sets the properties on the event being raised, all parameters are optional except for the action being performed
/// </summary>
/// <param name="action">An action based on the AuditEvent enum</param>
/// <param name="ipAddress">The client's IP address. This is usually automatically set but could be overridden if necessary</param>
/// <param name="performingUser">The Id of the user performing the action (if different from the user affected by the action)</param>
[Obsolete("Use the method that has the affectedUser parameter instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public IdentityAuditEventArgs(AuditEvent action, string ipAddress, int performingUser = -1)
{
DateTimeUtc = DateTime.UtcNow;
@@ -63,6 +60,35 @@ namespace Umbraco.Core.Auditing
: performingUser;
}
/// <summary>
/// Default constructor
/// </summary>
/// <param name="action"></param>
/// <param name="ipAddress"></param>
/// <param name="comment"></param>
/// <param name="performingUser"></param>
/// <param name="affectedUser"></param>
public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string comment = null, int performingUser = -1, int affectedUser = -1)
{
DateTimeUtc = DateTime.UtcNow;
Action = action;
IpAddress = ipAddress;
Comment = comment;
AffectedUser = affectedUser;
PerformingUser = performingUser == -1
? GetCurrentRequestBackofficeUserId()
: performingUser;
}
/// <summary>
/// Creates an instance without a performing or affected user (the id will be set to -1)
/// </summary>
/// <param name="action"></param>
/// <param name="ipAddress"></param>
/// <param name="username"></param>
/// <param name="comment"></param>
public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string username, string comment)
{
DateTimeUtc = DateTime.UtcNow;
@@ -71,6 +97,22 @@ namespace Umbraco.Core.Auditing
IpAddress = ipAddress;
Username = username;
Comment = comment;
PerformingUser = -1;
}
public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string username, string comment, int performingUser)
{
DateTimeUtc = DateTime.UtcNow;
Action = action;
IpAddress = ipAddress;
Username = username;
Comment = comment;
PerformingUser = performingUser == -1
? GetCurrentRequestBackofficeUserId()
: performingUser;
}
/// <summary>
@@ -1,10 +1,14 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Xml.Linq;
using ClientDependency.Core.CompositeFiles.Providers;
using ClientDependency.Core.Config;
using Semver;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
@@ -24,10 +28,74 @@ namespace Umbraco.Core.Configuration
_logger = logger;
_fileName = IOHelper.MapPath(string.Format("{0}/ClientDependency.config", SystemDirectories.Config));
}
/// <summary>
/// Changes the version number in ClientDependency.config to a hashed value for the version and the DateTime.Day
/// </summary>
/// <param name="version">The <see cref="SemVersion">version</see> of Umbraco we're upgrading to</param>
/// <param name="date">A <see cref="DateTime">date</see> value to use in the hash to prevent this method from updating the version on each startup</param>
/// <param name="dateFormat">Allows the developer to specify the <see cref="string">date precision</see> for the hash (i.e. "yyyyMMdd" would be a precision for the day)</param>
/// <returns>Boolean to indicate succesful update of the ClientDependency.config file</returns>
public bool UpdateVersionNumber(SemVersion version, DateTime date, string dateFormat)
{
var byteContents = Encoding.Unicode.GetBytes(version + date.ToString(dateFormat));
//This is a way to convert a string to a long
//see https://www.codeproject.com/Articles/34309/Convert-String-to-bit-Integer
//We could much more easily use MD5 which would create us an INT but since that is not compliant with
//hashing standards we have to use SHA
int intHash;
using (var hash = SHA256.Create())
{
var bytes = hash.ComputeHash(byteContents);
var longResult = new[] { 0, 8, 16, 24 }
.Select(i => BitConverter.ToInt64(bytes, i))
.Aggregate((x, y) => x ^ y);
//CDF requires an INT, and although this isn't fail safe, it will work for our purposes. We are not hashing for crypto purposes
//so there could be some collisions with this conversion but it's not a problem for our purposes
//It's also important to note that the long.GetHashCode() implementation in .NET is this: return (int) this ^ (int) (this >> 32);
//which means that this value will not change per appdomain like some GetHashCode implementations.
intHash = longResult.GetHashCode();
}
try
{
var clientDependencyConfigXml = XDocument.Load(_fileName, LoadOptions.PreserveWhitespace);
if (clientDependencyConfigXml.Root != null)
{
var versionAttribute = clientDependencyConfigXml.Root.Attribute("version");
//Set the new version to the hashcode of now
var oldVersion = versionAttribute.Value;
var newVersion = Math.Abs(intHash).ToString();
//don't update if it's the same version
if (oldVersion == newVersion)
return false;
versionAttribute.SetValue(newVersion);
clientDependencyConfigXml.Save(_fileName, SaveOptions.DisableFormatting);
_logger.Info<ClientDependencyConfiguration>(string.Format("Updated version number from {0} to {1}", oldVersion, newVersion));
return true;
}
}
catch (Exception ex)
{
_logger.Error<ClientDependencyConfiguration>("Couldn't update ClientDependency version number", ex);
}
return false;
}
/// <summary>
/// Changes the version number in ClientDependency.config to a random value to avoid stale caches
/// </summary>
/// <seealso cref="UpdateVersionNumber(SemVersion, DateTime, string)" />
[Obsolete("Use the UpdateVersionNumber method specifying the version, date and dateFormat instead")]
public bool IncreaseVersionNumber()
{
try
@@ -107,4 +175,4 @@ namespace Umbraco.Core.Configuration
return success;
}
}
}
}
@@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration
{
public class UmbracoVersion
{
private static readonly Version Version = new Version("7.8.2");
private static readonly Version Version = new Version("7.9.0");
/// <summary>
/// Gets the current version of Umbraco.
+2 -1
View File
@@ -9,6 +9,7 @@ namespace Umbraco.Core
{
public const string AdminGroupAlias = "admin";
public const string SensitiveDataGroupAlias = "sensitiveData";
public const string TranslatorGroupAlias = "translator";
public const string BackOfficeAuthenticationType = "UmbracoBackOffice";
@@ -36,4 +37,4 @@ namespace Umbraco.Core
}
}
}
}
@@ -0,0 +1,18 @@
using System;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
namespace Umbraco.Core.Events
{
internal class ExportedMemberEventArgs : EventArgs
{
public IMember Member { get; }
public MemberExportModel Exported { get; }
public ExportedMemberEventArgs(IMember member, MemberExportModel exported)
{
Member = member;
Exported = exported;
}
}
}
+16
View File
@@ -0,0 +1,16 @@
using System;
namespace Umbraco.Core.Events
{
public class RolesEventArgs : EventArgs
{
public RolesEventArgs(int[] memberIds, string[] roles)
{
MemberIds = memberIds;
Roles = roles;
}
public int[] MemberIds { get; set; }
public string[] Roles { get; set; }
}
}
@@ -0,0 +1,19 @@
using System;
using Umbraco.Core.Models.Membership;
namespace Umbraco.Core.Events
{
internal class UserGroupWithUsers
{
public UserGroupWithUsers(IUserGroup userGroup, IUser[] addedUsers, IUser[] removedUsers)
{
UserGroup = userGroup;
AddedUsers = addedUsers;
RemovedUsers = removedUsers;
}
public IUserGroup UserGroup { get; }
public IUser[] AddedUsers { get; }
public IUser[] RemovedUsers { get; }
}
}
+94
View File
@@ -0,0 +1,94 @@
using System;
using System.Reflection;
using System.Runtime.Serialization;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents an audited event.
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
internal class AuditEntry : Entity, IAuditEntry
{
private static PropertySelectors _selectors;
private int _performingUserId;
private string _performingDetails;
private string _performingIp;
private int _affectedUserId;
private string _affectedDetails;
private string _eventType;
private string _eventDetails;
private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors());
private class PropertySelectors
{
public readonly PropertyInfo PerformingUserId = ExpressionHelper.GetPropertyInfo<AuditEntry, int>(x => x.PerformingUserId);
public readonly PropertyInfo PerformingDetails = ExpressionHelper.GetPropertyInfo<AuditEntry, string>(x => x.PerformingDetails);
public readonly PropertyInfo PerformingIp = ExpressionHelper.GetPropertyInfo<AuditEntry, string>(x => x.PerformingIp);
public readonly PropertyInfo AffectedUserId = ExpressionHelper.GetPropertyInfo<AuditEntry, int>(x => x.AffectedUserId);
public readonly PropertyInfo AffectedDetails = ExpressionHelper.GetPropertyInfo<AuditEntry, string>(x => x.AffectedDetails);
public readonly PropertyInfo EventType = ExpressionHelper.GetPropertyInfo<AuditEntry, string>(x => x.EventType);
public readonly PropertyInfo EventDetails = ExpressionHelper.GetPropertyInfo<AuditEntry, string>(x => x.EventDetails);
}
/// <inheritdoc />
public int PerformingUserId
{
get => _performingUserId;
set => SetPropertyValueAndDetectChanges(value, ref _performingUserId, Selectors.PerformingUserId);
}
/// <inheritdoc />
public string PerformingDetails
{
get => _performingDetails;
set => SetPropertyValueAndDetectChanges(value, ref _performingDetails, Selectors.PerformingDetails);
}
/// <inheritdoc />
public string PerformingIp
{
get => _performingIp;
set => SetPropertyValueAndDetectChanges(value, ref _performingIp, Selectors.PerformingIp);
}
/// <inheritdoc />
public DateTime EventDateUtc
{
get => CreateDate;
set => CreateDate = value;
}
/// <inheritdoc />
public int AffectedUserId
{
get => _affectedUserId;
set => SetPropertyValueAndDetectChanges(value, ref _affectedUserId, Selectors.AffectedUserId);
}
/// <inheritdoc />
public string AffectedDetails
{
get => _affectedDetails;
set => SetPropertyValueAndDetectChanges(value, ref _affectedDetails, Selectors.AffectedDetails);
}
/// <inheritdoc />
public string EventType
{
get => _eventType;
set => SetPropertyValueAndDetectChanges(value, ref _eventType, Selectors.EventType);
}
/// <inheritdoc />
public string EventDetails
{
get => _eventDetails;
set => SetPropertyValueAndDetectChanges(value, ref _eventDetails, Selectors.EventDetails);
}
}
}
+102
View File
@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a consent.
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
internal class Consent : Entity, IConsent
{
private static PropertySelectors _selector;
private bool _current;
private string _source;
private string _context;
private string _action;
private ConsentState _state;
private string _comment;
// ReSharper disable once ClassNeverInstantiated.Local
private class PropertySelectors
{
public readonly PropertyInfo Current = ExpressionHelper.GetPropertyInfo<Consent, bool>(x => x.Current);
public readonly PropertyInfo Source = ExpressionHelper.GetPropertyInfo<Consent, string>(x => x.Source);
public readonly PropertyInfo Context = ExpressionHelper.GetPropertyInfo<Consent, string>(x => x.Context);
public readonly PropertyInfo Action = ExpressionHelper.GetPropertyInfo<Consent, string>(x => x.Action);
public readonly PropertyInfo State = ExpressionHelper.GetPropertyInfo<Consent, ConsentState>(x => x.State);
public readonly PropertyInfo Comment = ExpressionHelper.GetPropertyInfo<Consent, string>(x => x.Comment);
}
private static PropertySelectors Selectors => _selector ?? (_selector = new PropertySelectors());
/// <inheritdoc />
public bool Current
{
get => _current;
set => SetPropertyValueAndDetectChanges(value, ref _current, Selectors.Current);
}
/// <inheritdoc />
public string Source
{
get => _source;
set
{
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value));
SetPropertyValueAndDetectChanges(value, ref _source, Selectors.Source);
}
}
/// <inheritdoc />
public string Context
{
get => _context;
set
{
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value));
SetPropertyValueAndDetectChanges(value, ref _context, Selectors.Context);
}
}
/// <inheritdoc />
public string Action
{
get => _action;
set
{
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value));
SetPropertyValueAndDetectChanges(value, ref _action, Selectors.Action);
}
}
/// <inheritdoc />
public ConsentState State
{
get => _state;
// note: we probably should validate the state here, but since the
// enum is [Flags] with many combinations, this could be expensive
set => SetPropertyValueAndDetectChanges(value, ref _state, Selectors.State);
}
/// <inheritdoc />
public string Comment
{
get => _comment;
set => SetPropertyValueAndDetectChanges(value, ref _comment, Selectors.Comment);
}
/// <inheritdoc />
public IEnumerable<IConsent> History => HistoryInternal;
/// <summary>
/// Gets the previous states of this consent.
/// </summary>
internal List<IConsent> HistoryInternal { get; set; }
}
}
@@ -0,0 +1,18 @@
namespace Umbraco.Core.Models
{
/// <summary>
/// Provides extension methods for the <see cref="IConsent"/> interface.
/// </summary>
public static class ConsentExtensions
{
/// <summary>
/// Determines whether the consent is granted.
/// </summary>
public static bool IsGranted(this IConsent consent) => (consent.State & ConsentState.Granted) > 0;
/// <summary>
/// Determines whether the consent is revoked.
/// </summary>
public static bool IsRevoked(this IConsent consent) => (consent.State & ConsentState.Revoked) > 0;
}
}
+38
View File
@@ -0,0 +1,38 @@
using System;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents the state of a consent.
/// </summary>
[Flags]
public enum ConsentState // : int
{
// note - this is a [Flags] enumeration
// on can create detailed flags such as:
//GrantedOptIn = Granted | 0x0001
//GrandedByForce = Granted | 0x0002
//
// 16 situations for each Pending/Granted/Revoked should be ok
/// <summary>
/// There is no consent.
/// </summary>
None = 0,
/// <summary>
/// Consent is pending and has not been granted yet.
/// </summary>
Pending = 0x10000,
/// <summary>
/// Consent has been granted.
/// </summary>
Granted = 0x20000,
/// <summary>
/// Consent has been revoked.
/// </summary>
Revoked = 0x40000
}
}
+23 -1
View File
@@ -544,6 +544,28 @@ namespace Umbraco.Core.Models
return false;
}
/// <summary>
/// Returns both instance dirty properties and property type properties
/// </summary>
/// <returns></returns>
public override IEnumerable<string> GetDirtyProperties()
{
var instanceProperties = base.GetDirtyProperties();
var propertyTypes = Properties.Where(x => x.IsDirty()).Select(x => x.Alias);
return instanceProperties.Concat(propertyTypes);
}
/// <summary>
/// Returns both instance dirty properties and property type properties
/// </summary>
/// <returns></returns>
internal override IEnumerable<string> GetPreviouslyDirtyProperties()
{
var instanceProperties = base.GetPreviouslyDirtyProperties();
var propertyTypes = Properties.Where(x => x.WasDirty()).Select(x => x.Alias);
return instanceProperties.Concat(propertyTypes);
}
#endregion
}
}
}
@@ -23,6 +23,13 @@ namespace Umbraco.Core.Models.EntityBase
: _propertyChangedInfo.Where(x => x.Value).Select(x => x.Key);
}
internal virtual IEnumerable<string> GetPreviouslyDirtyProperties()
{
return _lastPropertyChangedInfo == null
? Enumerable.Empty<string>()
: _lastPropertyChangedInfo.Where(x => x.Value).Select(x => x.Key);
}
private bool _changeTrackingEnabled = true;
/// <summary>
@@ -221,4 +228,4 @@ namespace Umbraco.Core.Models.EntityBase
}
}
}
}
}
+60
View File
@@ -0,0 +1,60 @@
using System;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents an audited event.
/// </summary>
/// <remarks>
/// <para>The free-form details properties can be used to capture relevant infos (for example,
/// a user email and identifier) at the time of the audited event, even though they may change
/// later on - but we want to keep a track of their value at that time.</para>
/// <para>Depending on audit loggers, these properties can be purely free-form text, or
/// contain json serialized objects.</para>
/// </remarks>
public interface IAuditEntry : IAggregateRoot, IRememberBeingDirty
{
/// <summary>
/// Gets or sets the identifier of the user triggering the audited event.
/// </summary>
int PerformingUserId { get; set; }
/// <summary>
/// Gets or sets free-form details about the user triggering the audited event.
/// </summary>
string PerformingDetails { get; set; }
/// <summary>
/// Gets or sets the IP address or the request triggering the audited event.
/// </summary>
string PerformingIp { get; set; }
/// <summary>
/// Gets or sets the date and time of the audited event.
/// </summary>
DateTime EventDateUtc { get; set; }
/// <summary>
/// Gets or sets the identifier of the user affected by the audited event.
/// </summary>
/// <remarks>Not used when no single user is affected by the event.</remarks>
int AffectedUserId { get; set; }
/// <summary>
/// Gets or sets free-form details about the entity affected by the audited event.
/// </summary>
/// <remarks>The entity affected by the event can be another user, a member...</remarks>
string AffectedDetails { get; set; }
/// <summary>
/// Gets or sets the type of the audited event.
/// </summary>
string EventType { get; set; }
/// <summary>
/// Gets or sets free-form details about the audited event.
/// </summary>
string EventDetails { get; set; }
}
}
+55
View File
@@ -0,0 +1,55 @@
using System.Collections.Generic;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a consent state.
/// </summary>
/// <remarks>
/// <para>A consent is fully identified by a source (whoever is consenting), a context (for
/// example, an application), and an action (whatever is consented).</para>
/// <para>A consent state registers the state of the consent (granted, revoked...).</para>
/// </remarks>
public interface IConsent : IAggregateRoot, IRememberBeingDirty
{
/// <summary>
/// Determines whether the consent entity represents the current state.
/// </summary>
bool Current { get; }
/// <summary>
/// Gets the unique identifier of whoever is consenting.
/// </summary>
string Source { get; }
/// <summary>
/// Gets the unique identifier of the context of the consent.
/// </summary>
/// <remarks>
/// <para>Represents the domain, application, scope... of the action.</para>
/// <para>When the action is a Udi, this should be the Udi type.</para>
/// </remarks>
string Context { get; }
/// <summary>
/// Gets the unique identifier of the consented action.
/// </summary>
string Action { get; }
/// <summary>
/// Gets the state of the consent.
/// </summary>
ConsentState State { get; }
/// <summary>
/// Gets some additional free text.
/// </summary>
string Comment { get; }
/// <summary>
/// Gets the previous states of this consent.
/// </summary>
IEnumerable<IConsent> History { get; }
}
}
+15 -1
View File
@@ -19,6 +19,13 @@
/// <returns></returns>
bool MemberCanViewProperty(string propertyTypeAlias);
/// <summary>
/// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile.
/// </summary>
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to check</param>
/// <returns></returns>
bool IsSensitiveProperty(string propertyTypeAlias);
/// <summary>
/// Sets a boolean indicating whether a Property is editable by the Member.
/// </summary>
@@ -32,5 +39,12 @@
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to set</param>
/// <param name="value">Boolean value, true or false</param>
void SetMemberCanViewProperty(string propertyTypeAlias, bool value);
/// <summary>
/// Sets a boolean indicating whether a Property is a sensitive value on the Members profile.
/// </summary>
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to set</param>
/// <param name="value">Boolean value, true or false</param>
void SetIsSensitiveProperty(string propertyTypeAlias, bool value);
}
}
}
@@ -34,6 +34,15 @@ namespace Umbraco.Core.Models.Identity
.ForMember(user => user.CalculatedContentStartNodeIds, expression => expression.MapFrom(user => user.CalculateContentStartNodeIds(applicationContext.Services.EntityService)))
.ForMember(user => user.CalculatedMediaStartNodeIds, expression => expression.MapFrom(user => user.CalculateMediaStartNodeIds(applicationContext.Services.EntityService)))
.ForMember(user => user.AllowedSections, expression => expression.MapFrom(user => user.AllowedSections.ToArray()))
.ForMember(user => user.LockoutEnabled, expression => expression.Ignore())
.ForMember(user => user.Logins, expression => expression.Ignore())
.ForMember(user => user.Roles, expression => expression.Ignore())
.ForMember(user => user.PhoneNumber, expression => expression.Ignore())
.ForMember(user => user.PhoneNumberConfirmed, expression => expression.Ignore())
.ForMember(user => user.TwoFactorEnabled, expression => expression.Ignore())
.ForMember(user => user.Claims, expression => expression.Ignore())
.AfterMap((user, identityUser) =>
{
identityUser.ResetDirtyProperties(true);
@@ -47,4 +56,4 @@ namespace Umbraco.Core.Models.Identity
return storedPass.StartsWith(Constants.Security.EmptyPasswordPrefix) ? null : storedPass;
}
}
}
}
+14 -2
View File
@@ -170,7 +170,19 @@ namespace Umbraco.Core.Models
public string RawPasswordValue
{
get { return _rawPasswordValue; }
set { SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector); }
set
{
if (value == null)
{
//special case, this is used to ensure that the password is not updated when persisting, in this case
//we don't want to track changes either
_rawPasswordValue = null;
}
else
{
SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector);
}
}
}
/// <summary>
@@ -628,4 +640,4 @@ namespace Umbraco.Core.Models
}
}
}
}
+49 -13
View File
@@ -64,7 +64,7 @@ namespace Umbraco.Core.Models
}
/// <summary>
/// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile) by the PropertyTypes' alias.
/// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile, IsSensitive) by the PropertyTypes' alias.
/// </summary>
[DataMember]
internal IDictionary<string, MemberTypePropertyProfileAccess> MemberTypePropertyTypes { get; private set; }
@@ -76,11 +76,11 @@ namespace Umbraco.Core.Models
/// <returns></returns>
public bool MemberCanEditProperty(string propertyTypeAlias)
{
if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias))
MemberTypePropertyProfileAccess propertyProfile;
if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile))
{
return MemberTypePropertyTypes[propertyTypeAlias].IsEditable;
return propertyProfile.IsEditable;
}
return false;
}
@@ -91,11 +91,26 @@ namespace Umbraco.Core.Models
/// <returns></returns>
public bool MemberCanViewProperty(string propertyTypeAlias)
{
if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias))
MemberTypePropertyProfileAccess propertyProfile;
if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile))
{
return MemberTypePropertyTypes[propertyTypeAlias].IsVisible;
return propertyProfile.IsVisible;
}
return false;
}
/// <summary>
/// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile.
/// </summary>
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to check</param>
/// <returns></returns>
public bool IsSensitiveProperty(string propertyTypeAlias)
{
MemberTypePropertyProfileAccess propertyProfile;
if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile))
{
return propertyProfile.IsSensitive;
}
return false;
}
@@ -106,13 +121,14 @@ namespace Umbraco.Core.Models
/// <param name="value">Boolean value, true or false</param>
public void SetMemberCanEditProperty(string propertyTypeAlias, bool value)
{
if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias))
MemberTypePropertyProfileAccess propertyProfile;
if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile))
{
MemberTypePropertyTypes[propertyTypeAlias].IsEditable = value;
propertyProfile.IsEditable = value;
}
else
{
var tuple = new MemberTypePropertyProfileAccess(false, value);
var tuple = new MemberTypePropertyProfileAccess(false, value, false);
MemberTypePropertyTypes.Add(propertyTypeAlias, tuple);
}
}
@@ -124,15 +140,35 @@ namespace Umbraco.Core.Models
/// <param name="value">Boolean value, true or false</param>
public void SetMemberCanViewProperty(string propertyTypeAlias, bool value)
{
if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias))
MemberTypePropertyProfileAccess propertyProfile;
if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile))
{
MemberTypePropertyTypes[propertyTypeAlias].IsVisible = value;
propertyProfile.IsVisible = value;
}
else
{
var tuple = new MemberTypePropertyProfileAccess(value, false);
var tuple = new MemberTypePropertyProfileAccess(value, false, false);
MemberTypePropertyTypes.Add(propertyTypeAlias, tuple);
}
}
/// <summary>
/// Sets a boolean indicating whether a Property is a sensitive value on the Members profile.
/// </summary>
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to set</param>
/// <param name="value">Boolean value, true or false</param>
public void SetIsSensitiveProperty(string propertyTypeAlias, bool value)
{
MemberTypePropertyProfileAccess propertyProfile;
if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile))
{
propertyProfile.IsSensitive = value;
}
else
{
var tuple = new MemberTypePropertyProfileAccess(false, false, true);
MemberTypePropertyTypes.Add(propertyTypeAlias, tuple);
}
}
}
}
}
@@ -5,13 +5,15 @@ namespace Umbraco.Core.Models
/// </summary>
internal class MemberTypePropertyProfileAccess
{
public MemberTypePropertyProfileAccess(bool isVisible, bool isEditable)
public MemberTypePropertyProfileAccess(bool isVisible, bool isEditable, bool isSenstive)
{
IsVisible = isVisible;
IsEditable = isEditable;
IsSensitive = isSenstive;
}
public bool IsVisible { get; set; }
public bool IsEditable { get; set; }
public bool IsSensitive { get; set; }
}
}
}
@@ -63,4 +63,4 @@ namespace Umbraco.Core.Models.Membership
}
}
}
}
@@ -3,7 +3,7 @@ using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models.Membership
{
public interface IUserGroup : IAggregateRoot
public interface IUserGroup : IAggregateRoot, IRememberBeingDirty, ICanBeDirty
{
string Alias { get; set; }
@@ -41,4 +41,4 @@ namespace Umbraco.Core.Models.Membership
/// </summary>
int UserCount { get; }
}
}
}
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Umbraco.Core.Models.Membership
{
internal class MemberExportModel
{
public int Id { get; set; }
public Guid Key { get; set; }
public string Name { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public List<string> Groups { get; set; }
public string ContentTypeAlias { get; set; }
public DateTime CreateDate { get; set; }
public DateTime UpdateDate { get; set; }
public List<MemberExportProperty> Properties { get; set; }
}
}
@@ -0,0 +1,14 @@
using System;
namespace Umbraco.Core.Models.Membership
{
internal class MemberExportProperty
{
public int Id { get; set; }
public string Alias { get; set; }
public string Name { get; set; }
public object Value { get; set; }
public DateTime? CreateDate { get; set; }
public DateTime? UpdateDate { get; set; }
}
}
+2 -1
View File
@@ -706,5 +706,6 @@ namespace Umbraco.Core.Models.Membership
return _user.GetHashCode();
}
}
}
}
}
@@ -152,4 +152,4 @@ namespace Umbraco.Core.Models.Membership
public int UserCount { get; private set; }
}
}
}
+1 -2
View File
@@ -1,6 +1,5 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
@@ -436,4 +435,4 @@ namespace Umbraco.Core.Models
return clone;
}
}
}
}
@@ -42,7 +42,7 @@ namespace Umbraco.Core.Models.PublishedContent
var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias;
if (constructors.ContainsKey(typeName))
throw new InvalidOperationException(string.Format("More that one type want to be a model for content type {0}.", typeName));
throw new InvalidOperationException(string.Format("More than one type wants to be a model for content type {0}.", typeName));
// should work everywhere, but slow
//_constructors[typeName] = constructor;
@@ -0,0 +1,60 @@
using System;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseAnnotations;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
namespace Umbraco.Core.Models.Rdbms
{
[TableName(TableName)]
[PrimaryKey("id")]
[ExplicitColumns]
internal class AuditEntryDto
{
public const string TableName = "umbracoAudit";
public const int IpLength = 64;
public const int EventTypeLength = 256;
public const int DetailsLength = 1024;
[Column("id")]
[PrimaryKeyColumn]
public int Id { get; set; }
// there is NO foreign key to the users table here, neither for performing user nor for
// affected user, so we can delete users and NOT delete the associated audit trails, and
// users can still be identified via the details free-form text fields.
[Column("performingUserId")]
public int PerformingUserId { get; set; }
[Column("performingDetails")]
[NullSetting(NullSetting = NullSettings.Null)]
[Length(DetailsLength)]
public string PerformingDetails { get; set; }
[Column("performingIp")]
[NullSetting(NullSetting = NullSettings.Null)]
[Length(IpLength)]
public string PerformingIp { get; set; }
[Column("eventDateUtc")]
[Constraint(Default = SystemMethods.CurrentDateTime)]
public DateTime EventDateUtc { get; set; }
[Column("affectedUserId")]
public int AffectedUserId { get; set; }
[Column("affectedDetails")]
[NullSetting(NullSetting = NullSettings.Null)]
[Length(DetailsLength)]
public string AffectedDetails { get; set; }
[Column("eventType")]
[Length(EventTypeLength)]
public string EventType { get; set; }
[Column("eventDetails")]
[NullSetting(NullSetting = NullSettings.Null)]
[Length(DetailsLength)]
public string EventDetails { get; set; }
}
}
@@ -0,0 +1,44 @@
using System;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseAnnotations;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
namespace Umbraco.Core.Models.Rdbms
{
[TableName(TableName)]
[PrimaryKey("id")]
[ExplicitColumns]
public class ConsentDto
{
internal const string TableName = "umbracoConsent";
[Column("id")]
[PrimaryKeyColumn]
public int Id { get; set; }
[Column("current")]
public bool Current { get; set; }
[Column("source")]
[Length(512)]
public string Source { get; set; }
[Column("context")]
[Length(128)]
public string Context { get; set; }
[Column("action")]
[Length(512)]
public string Action { get; set; }
[Column("createDate")]
[Constraint(Default = SystemMethods.CurrentDateTime)]
public DateTime CreateDate { get; set; }
[Column("state")]
public int State { get; set; }
[Column("comment")]
public string Comment { get; set; }
}
}
@@ -27,5 +27,9 @@ namespace Umbraco.Core.Models.Rdbms
[Column("viewOnProfile")]
[Constraint(Default = "0")]
public bool ViewOnProfile { get; set; }
[Column("isSensitive")]
[Constraint(Default = "0")]
public bool IsSensitive { get; set; }
}
}
}
@@ -45,6 +45,9 @@ namespace Umbraco.Core.Models.Rdbms
[Column("viewOnProfile")]
public bool ViewOnProfile { get; set; }
[Column("isSensitive")]
public bool IsSensitive { get; set; }
/* cmsDataType */
[Column("propertyEditorAlias")]
public string PropertyEditorAlias { get; set; }
@@ -52,7 +55,8 @@ namespace Umbraco.Core.Models.Rdbms
[Column("dbType")]
public string DbType { get; set; }
[Column("UniqueID")]
[Column("UniqueID")]
public Guid UniqueId { get; set; }
}
}
}
@@ -17,7 +17,7 @@ namespace Umbraco.Core.Models.Rdbms
}
[Column("id")]
[PrimaryKeyColumn(IdentitySeed = 5)]
[PrimaryKeyColumn(IdentitySeed = 6)]
public int Id { get; set; }
[Column("userGroupAlias")]
@@ -68,4 +68,4 @@ namespace Umbraco.Core.Models.Rdbms
[ResultColumn]
public int UserCount { get; set; }
}
}
}
+11 -1
View File
@@ -261,6 +261,16 @@ namespace Umbraco.Core.Models
return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.AdminGroupAlias);
}
/// <summary>
/// Determines whether this user has access to view sensitive data
/// </summary>
/// <param name="user"></param>
public static bool HasAccessToSensitiveData(this IUser user)
{
if (user == null) throw new ArgumentNullException("user");
return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.SensitiveDataGroupAlias);
}
// calc. start nodes, combining groups' and user's, and excluding what's in the bin
public static int[] CalculateContentStartNodeIds(this IUser user, IEntityService entityService)
{
@@ -413,4 +423,4 @@ namespace Umbraco.Core.Models
return lsn.ToArray();
}
}
}
}
@@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
namespace Umbraco.Core.Persistence.Factories
{
internal static class AuditEntryFactory
{
public static IEnumerable<IAuditEntry> BuildEntities(IEnumerable<AuditEntryDto> dtos)
{
return dtos.Select(BuildEntity).ToList();
}
public static IAuditEntry BuildEntity(AuditEntryDto dto)
{
var entity = new AuditEntry
{
Id = dto.Id,
PerformingUserId = dto.PerformingUserId,
PerformingDetails = dto.PerformingDetails,
PerformingIp = dto.PerformingIp,
EventDateUtc = dto.EventDateUtc,
AffectedUserId = dto.AffectedUserId,
AffectedDetails = dto.AffectedDetails,
EventType = dto.EventType,
EventDetails = dto.EventDetails
};
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
entity.ResetDirtyProperties(false);
return entity;
}
public static AuditEntryDto BuildDto(IAuditEntry entity)
{
return new AuditEntryDto
{
Id = entity.Id,
PerformingUserId = entity.PerformingUserId,
PerformingDetails = entity.PerformingDetails,
PerformingIp = entity.PerformingIp,
EventDateUtc = entity.EventDateUtc,
AffectedUserId = entity.AffectedUserId,
AffectedDetails = entity.AffectedDetails,
EventType = entity.EventType,
EventDetails = entity.EventDetails
};
}
}
}
@@ -0,0 +1,65 @@
using System.Collections.Generic;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
namespace Umbraco.Core.Persistence.Factories
{
internal static class ConsentFactory
{
public static IEnumerable<IConsent> BuildEntities(IEnumerable<ConsentDto> dtos)
{
var ix = new Dictionary<string, Consent>();
var output = new List<Consent>();
foreach (var dto in dtos)
{
var k = dto.Source + "::" + dto.Context + "::" + dto.Action;
var consent = new Consent
{
Id = dto.Id,
Current = dto.Current,
CreateDate = dto.CreateDate,
Source = dto.Source,
Context = dto.Context,
Action = dto.Action,
State = (ConsentState) dto.State, // assume value is valid
Comment = dto.Comment
};
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
consent.ResetDirtyProperties(false);
if (ix.TryGetValue(k, out var current))
{
if (current.HistoryInternal == null)
current.HistoryInternal = new List<IConsent>();
current.HistoryInternal.Add(consent);
}
else
{
ix[k] = consent;
output.Add(consent);
}
}
return output;
}
public static ConsentDto BuildDto(IConsent entity)
{
return new ConsentDto
{
Id = entity.Id,
Current = entity.Current,
CreateDate = entity.CreateDate,
Source = entity.Source,
Context = entity.Context,
Action = entity.Action,
State = (int) entity.State,
Comment = entity.Comment
};
}
}
}
@@ -84,7 +84,8 @@ namespace Umbraco.Core.Persistence.Factories
NodeId = entity.Id,
PropertyTypeId = x.Id,
CanEdit = memberType.MemberCanEditProperty(x.Alias),
ViewOnProfile = memberType.MemberCanViewProperty(x.Alias)
ViewOnProfile = memberType.MemberCanViewProperty(x.Alias),
IsSensitive = memberType.IsSensitiveProperty(x.Alias)
}).ToList();
return dtos;
}
@@ -159,4 +160,4 @@ namespace Umbraco.Core.Persistence.Factories
#endregion
}
}
}
@@ -56,10 +56,10 @@ namespace Umbraco.Core.Persistence.Factories
//Add the standard PropertyType to the current list
propertyTypes.Add(standardPropertyType.Value);
//Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType
//Internal dictionary for adding "MemberCanEdit", "VisibleOnProfile", "IsSensitive" properties to each PropertyType
memberType.MemberTypePropertyTypes.Add(standardPropertyType.Key,
new MemberTypePropertyProfileAccess(false, false));
new MemberTypePropertyProfileAccess(false, false, false));
}
memberType.NoGroupPropertyTypes = propertyTypes;
@@ -102,7 +102,7 @@ namespace Umbraco.Core.Persistence.Factories
{
//Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType
memberType.MemberTypePropertyTypes.Add(typeDto.Alias,
new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit));
new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit, typeDto.IsSensitive));
var tempGroupDto = groupDto;
@@ -157,7 +157,7 @@ namespace Umbraco.Core.Persistence.Factories
{
//Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType
memberType.MemberTypePropertyTypes.Add(typeDto.Alias,
new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit));
new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit, typeDto.IsSensitive));
//ensures that any built-in membership properties have their correct dbtype assigned no matter
//what the underlying data type is
@@ -198,4 +198,4 @@ namespace Umbraco.Core.Persistence.Factories
}
}
}
}
@@ -0,0 +1,41 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
namespace Umbraco.Core.Persistence.Mappers
{
/// <summary>
/// Represents a mapper for audit entry entities.
/// </summary>
[MapperFor(typeof(IAuditEntry))]
[MapperFor(typeof(AuditEntry))]
public sealed class AuditEntryMapper : BaseMapper
{
private static readonly ConcurrentDictionary<string, DtoMapModel> PropertyInfoCacheInstance
= new ConcurrentDictionary<string, DtoMapModel>();
/// <summary>
/// Initializes a new instance of the <see cref="AuditEntryMapper"/> class.
/// </summary>
public AuditEntryMapper()
{
// note: why the base ctor does not invoke BuildMap is a mystery to me
BuildMap();
}
internal override ConcurrentDictionary<string, DtoMapModel> PropertyInfoCache => PropertyInfoCacheInstance;
internal override void BuildMap()
{
CacheMap<AuditEntry, AuditEntryDto>(entity => entity.Id, dto => dto.Id);
CacheMap<AuditEntry, AuditEntryDto>(entity => entity.PerformingUserId, dto => dto.PerformingUserId);
CacheMap<AuditEntry, AuditEntryDto>(entity => entity.PerformingDetails, dto => dto.PerformingDetails);
CacheMap<AuditEntry, AuditEntryDto>(entity => entity.PerformingIp, dto => dto.PerformingIp);
CacheMap<AuditEntry, AuditEntryDto>(entity => entity.EventDateUtc, dto => dto.EventDateUtc);
CacheMap<AuditEntry, AuditEntryDto>(entity => entity.AffectedUserId, dto => dto.AffectedUserId);
CacheMap<AuditEntry, AuditEntryDto>(entity => entity.AffectedDetails, dto => dto.AffectedDetails);
CacheMap<AuditEntry, AuditEntryDto>(entity => entity.EventType, dto => dto.EventType);
CacheMap<AuditEntry, AuditEntryDto>(entity => entity.EventDetails, dto => dto.EventDetails);
}
}
}
@@ -0,0 +1,40 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
namespace Umbraco.Core.Persistence.Mappers
{
/// <summary>
/// Represents a mapper for consent entities.
/// </summary>
[MapperFor(typeof(IConsent))]
[MapperFor(typeof(Consent))]
public sealed class ConsentMapper : BaseMapper
{
private static readonly ConcurrentDictionary<string, DtoMapModel> PropertyInfoCacheInstance
= new ConcurrentDictionary<string, DtoMapModel>();
/// <summary>
/// Initializes a new instance of the <see cref="ConsentMapper"/> class.
/// </summary>
public ConsentMapper()
{
// note: why the base ctor does not invoke BuildMap is a mystery to me
BuildMap();
}
internal override ConcurrentDictionary<string, DtoMapModel> PropertyInfoCache => PropertyInfoCacheInstance;
internal override void BuildMap()
{
CacheMap<Consent, ConsentDto>(entity => entity.Id, dto => dto.Id);
CacheMap<Consent, ConsentDto>(entity => entity.Current, dto => dto.Current);
CacheMap<Consent, ConsentDto>(entity => entity.CreateDate, dto => dto.CreateDate);
CacheMap<Consent, ConsentDto>(entity => entity.Source, dto => dto.Source);
CacheMap<Consent, ConsentDto>(entity => entity.Context, dto => dto.Context);
CacheMap<Consent, ConsentDto>(entity => entity.Action, dto => dto.Action);
CacheMap<Consent, ConsentDto>(entity => entity.State, dto => dto.State);
CacheMap<Consent, ConsentDto>(entity => entity.Comment, dto => dto.Comment);
}
}
}
@@ -178,11 +178,13 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
_database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = "writer", Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" });
_database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" });
_database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 4, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.TranslatorGroupAlias, Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-globe" });
_database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 5, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" });
}
private void CreateUmbracoUser2UserGroupData()
{
_database.Insert(new User2UserGroupDto { UserGroupId = 1, UserId = 0 });
_database.Insert(new User2UserGroupDto { UserGroupId = 1, UserId = 0 }); //add admin to admins
_database.Insert(new User2UserGroupDto { UserGroupId = 5, UserId = 0 }); //add admin to sensitive data
}
private void CreateUmbracoUserGroup2AppData()
@@ -337,4 +339,4 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
_database.Insert("umbracoMigration", "pk", false, dto);
}
}
}
}
@@ -92,7 +92,9 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
{52, typeof (UserGroup2NodePermissionDto) },
{53, typeof (UserGroup2AppDto) },
{54, typeof (UserStartNodeDto) },
{55, typeof (UserLoginDto)}
{55, typeof (UserLoginDto)},
{56, typeof (ConsentDto)},
{57, typeof (AuditEntryDto)}
};
#endregion
@@ -378,4 +380,4 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
#endregion
}
}
}
@@ -154,6 +154,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
return new Version(7, 7, 0);
}
//if the error is for isSensitive column it must be the previous version to 7.9 since that is when it is added
if (Errors.Any(x => x.Item1.Equals("Column") && (x.Item2.InvariantEquals("cmsMemberType,isSensitive"))))
{
return new Version(7, 8, 0);
}
return UmbracoVersion.Current;
}
@@ -214,4 +220,4 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
return sb.ToString();
}
}
}
}
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.DatabaseAnnotations;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero
{
[Migration("7.9.0", 1, Constants.System.UmbracoMigrationName)]
public class AddIsSensitiveMemberTypeColumn : MigrationBase
{
public AddIsSensitiveMemberTypeColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger)
: base(sqlSyntax, logger)
{
}
public override void Up()
{
//Don't exeucte if the column is already there
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray();
if (columns.Any(x => x.TableName.InvariantEquals("cmsMemberType") && x.ColumnName.InvariantEquals("isSensitive")) == false)
{
Create.Column("isSensitive").OnTable("cmsMemberType").AsBoolean().WithDefaultValue(0).NotNullable();
}
}
public override void Down()
{
}
}
}
@@ -0,0 +1,29 @@
using System;
using System.Linq;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenNineZero
{
[Migration("7.9.0", 1, Constants.System.UmbracoMigrationName)]
public class AddUmbracoAuditTable : MigrationBase
{
public AddUmbracoAuditTable(ISqlSyntaxProvider sqlSyntax, ILogger logger)
: base(sqlSyntax, logger)
{ }
public override void Up()
{
var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray();
if (tables.InvariantContains(AuditEntryDto.TableName))
return;
Create.Table<AuditEntryDto>();
}
public override void Down()
{
throw new NotSupportedException();
}
}
}
@@ -0,0 +1,29 @@
using System.Linq;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenNineZero
{
[Migration("7.9.0", 1, Constants.System.UmbracoMigrationName)]
public class AddUmbracoConsentTable : MigrationBase
{
public AddUmbracoConsentTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger)
{
}
public override void Up()
{
var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray();
if (tables.InvariantContains(ConsentDto.TableName))
return;
Create.Table<ConsentDto>();
}
public override void Down()
{
}
}
}
@@ -0,0 +1,37 @@
using System;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenNineZero
{
[Migration("7.9.0", 2, Constants.System.UmbracoMigrationName)]
public class CreateSensitiveDataUserGroup : MigrationBase
{
public CreateSensitiveDataUserGroup(ISqlSyntaxProvider sqlSyntax, ILogger logger)
: base(sqlSyntax, logger)
{
}
public override void Up()
{
Execute.Code(database =>
{
//Don't exeucte if the group is already there
var exists = database.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoUserGroup WHERE userGroupAlias = @userGroupAlias",
new {userGroupAlias = Constants.Security.SensitiveDataGroupAlias });
if (exists == 0)
{
var resultId = database.Insert("umbracoUserGroup", "id", new UserGroupDto { StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" });
database.Insert(new User2UserGroupDto { UserGroupId = Convert.ToInt32(resultId), UserId = 0 }); //add admin to sensitive data
}
return string.Empty;
});
}
public override void Down()
{
}
}
}
+8 -7
View File
@@ -402,7 +402,7 @@ namespace Umbraco.Core.Persistence
}
// Helper to handle named parameters from object properties
static readonly Regex rxParams = new Regex(@"(?<!@)@\w+", RegexOptions.Compiled);
static Regex rxParams = new Regex(@"(?<!@)@\w+", RegexOptions.Compiled);
public static string ProcessParams(string _sql, object[] args_src, List<object> args_dest)
{
return rxParams.Replace(_sql, m =>
@@ -545,7 +545,7 @@ namespace Umbraco.Core.Persistence
}
// Create a command
static readonly Regex rxParamsPrefix = new Regex(@"(?<!@)@\w+", RegexOptions.Compiled);
static Regex rxParamsPrefix = new Regex(@"(?<!@)@\w+", RegexOptions.Compiled);
public IDbCommand CreateCommand(IDbConnection connection, string sql, params object[] args)
{
// Perform named argument replacements
@@ -666,8 +666,8 @@ namespace Umbraco.Core.Persistence
return ExecuteScalar<T>(sql.SQL, sql.Arguments);
}
static readonly Regex rxSelect = new Regex(@"\A\s*(SELECT|EXECUTE|CALL)\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline);
static readonly Regex rxFrom = new Regex(@"\A\s*FROM\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline);
Regex rxSelect = new Regex(@"\A\s*(SELECT|EXECUTE|CALL)\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline);
Regex rxFrom = new Regex(@"\A\s*FROM\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline);
string AddSelectClause<T>(string sql)
{
if (sql.StartsWith(";"))
@@ -701,9 +701,9 @@ namespace Umbraco.Core.Persistence
return Fetch<T>(sql.SQL, sql.Arguments);
}
static readonly Regex rxColumns = new Regex(@"\A\s*SELECT\s+((?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?<!,\s+)\bFROM\b", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
static readonly Regex rxOrderBy = new Regex(@"\bORDER\s+BY\s+(?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?(?:\s*,\s*(?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
static readonly Regex rxDistinct = new Regex(@"\ADISTINCT\s", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
static Regex rxColumns = new Regex(@"\A\s*SELECT\s+((?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?<!,\s+)\bFROM\b", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
static Regex rxOrderBy = new Regex(@"\bORDER\s+BY\s+(?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?(?:\s*,\s*(?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
static Regex rxDistinct = new Regex(@"\ADISTINCT\s", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
public static bool SplitSqlForPaging(string sql, out string sqlCount, out string sqlSelectRemoved, out string sqlOrderBy)
{
sqlSelectRemoved = null;
@@ -2546,4 +2546,5 @@ namespace Umbraco.Core.Persistence
}
}
}
}
@@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Core.Persistence.Repositories
{
/// <summary>
/// Represents the PetaPoco implementatino of <see cref="IAuditEntryRepository"/>.
/// </summary>
internal class AuditEntryRepository : PetaPocoRepositoryBase<int, IAuditEntry>, IAuditEntryRepository
{
/// <summary>
/// Initializes a new instance of the <see cref="AuditEntryRepository"/> class.
/// </summary>
public AuditEntryRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
{ }
protected override Guid NodeObjectTypeId => throw new NotSupportedException();
/// <inheritdoc />
protected override IAuditEntry PerformGet(int id)
{
var sql = new Sql()
.Select("*")
.From<AuditEntryDto>(SqlSyntax)
.Where<AuditEntryDto>(x => x.Id == id, SqlSyntax);
var dto = Database.FirstOrDefault<AuditEntryDto>(sql);
return dto == null ? null : AuditEntryFactory.BuildEntity(dto); }
/// <inheritdoc />
protected override IEnumerable<IAuditEntry> PerformGetAll(params int[] ids)
{
if (ids.Length == 0)
{
var sql = new Sql()
.Select("*")
.From<AuditEntryDto>(SqlSyntax);
return Database.Fetch<AuditEntryDto>(sql).Select(AuditEntryFactory.BuildEntity);
}
var entries = new List<IAuditEntry>();
foreach (var group in ids.InGroupsOf(2000))
{
var sql = new Sql()
.Select("*")
.From<AuditEntryDto>(SqlSyntax)
.WhereIn<AuditEntryDto>(x => x.Id, group, SqlSyntax);
entries.AddRange(Database.Fetch<AuditEntryDto>(sql).Select(AuditEntryFactory.BuildEntity));
}
return entries;
}
/// <inheritdoc />
protected override IEnumerable<IAuditEntry> PerformGetByQuery(IQuery<IAuditEntry> query)
{
var sqlClause = GetBaseQuery(false);
var translator = new SqlTranslator<IAuditEntry>(sqlClause, query);
var sql = translator.Translate();
return Database.Fetch<AuditEntryDto>(sql).Select(AuditEntryFactory.BuildEntity);
}
/// <inheritdoc />
protected override Sql GetBaseQuery(bool isCount)
{
return new Sql().Select(isCount ? "COUNT(*)" : "*").From<AuditEntryDto>(SqlSyntax);
}
/// <inheritdoc />
protected override string GetBaseWhereClause()
{
return $"{AuditEntryDto.TableName}.id = @Id";
}
/// <inheritdoc />
protected override IEnumerable<string> GetDeleteClauses()
{
throw new NotSupportedException("Audit entries cannot be deleted.");
}
/// <inheritdoc />
protected override void PersistNewItem(IAuditEntry entity)
{
((Entity) entity).AddingEntity();
var dto = AuditEntryFactory.BuildDto(entity);
Database.Insert(dto);
entity.Id = dto.Id;
entity.ResetDirtyProperties();
}
/// <inheritdoc />
protected override void PersistUpdatedItem(IAuditEntry entity)
{
throw new NotSupportedException("Audit entries cannot be updated.");
}
/// <inheritdoc />
public IEnumerable<IAuditEntry> GetPage(long pageIndex, int pageCount, out long records)
{
var sql = new Sql()
.Select("*")
.From<AuditEntryDto>(SqlSyntax)
.OrderByDescending<AuditEntryDto>(x => x.EventDateUtc, SqlSyntax);
var page = Database.Page<AuditEntryDto>(pageIndex + 1, pageCount, sql);
records = page.TotalItems;
return page.Items.Select(AuditEntryFactory.BuildEntity);
}
/// <inheritdoc />
public bool IsAvailable()
{
var tables = SqlSyntax.GetTablesInSchema(Database).ToArray();
return tables.InvariantContains(AuditEntryDto.TableName);
}
}
}
@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Core.Persistence.Repositories
{
/// <summary>
/// Represents the PetaPoco implementation of <see cref="IConsentRepository"/>.
/// </summary>
internal class ConsentRepository : PetaPocoRepositoryBase<int, IConsent>, IConsentRepository
{
/// <summary>
/// Initializes a new instance of the <see cref="ConsentRepository"/> class.
/// </summary>
public ConsentRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
{ }
/// <inheritdoc />
protected override Guid NodeObjectTypeId => throw new NotSupportedException();
/// <inheritdoc />
public void ClearCurrent(string source, string context, string action)
{
var sql = new Sql($@"UPDATE {SqlSyntax.GetQuotedTableName(ConsentDto.TableName)}
SET {SqlSyntax.GetQuotedColumnName("current")} = @0
WHERE {SqlSyntax.GetQuotedColumnName("source")} = @1 AND {SqlSyntax.GetQuotedColumnName("context")} = @2 AND {SqlSyntax.GetQuotedColumnName("action")} = @3
AND {SqlSyntax.GetQuotedColumnName("current")} <> @0 ", false, source, context, action);
Database.Execute(sql);
}
/// <inheritdoc />
protected override IConsent PerformGet(int id)
{
throw new NotImplementedException();
}
/// <inheritdoc />
protected override IEnumerable<IConsent> PerformGetAll(params int[] ids)
{
throw new NotImplementedException();
}
/// <inheritdoc />
protected override IEnumerable<IConsent> PerformGetByQuery(IQuery<IConsent> query)
{
var sqlClause = new Sql().Select("*").From<ConsentDto>(SqlSyntax);
var translator = new SqlTranslator<IConsent>(sqlClause, query);
var sql = translator.Translate().OrderByDescending<ConsentDto>(x => x.CreateDate, SqlSyntax);
return ConsentFactory.BuildEntities(Database.Fetch<ConsentDto>(sql));
}
/// <inheritdoc />
protected override Sql GetBaseQuery(bool isCount)
{
throw new NotImplementedException();
}
/// <inheritdoc />
protected override string GetBaseWhereClause()
{
throw new NotImplementedException();
}
/// <inheritdoc />
protected override IEnumerable<string> GetDeleteClauses()
{
throw new NotImplementedException();
}
/// <inheritdoc />
protected override void PersistNewItem(IConsent entity)
{
((Entity) entity).AddingEntity();
var dto = ConsentFactory.BuildDto(entity);
Database.Insert(dto);
entity.Id = dto.Id;
entity.ResetDirtyProperties();
}
/// <inheritdoc />
protected override void PersistUpdatedItem(IConsent entity)
{
((Entity) entity).UpdatingEntity();
var dto = ConsentFactory.BuildDto(entity);
Database.Update(dto);
entity.ResetDirtyProperties();
IsolatedCache.ClearCacheItem(GetCacheIdKey<IConsent>(entity.Id));
}
}
}
@@ -0,0 +1,22 @@
using System.Collections.Generic;
using Umbraco.Core.Models;
namespace Umbraco.Core.Persistence.Repositories
{
/// <summary>
/// Represents a repository for <see cref="IAuditEntry"/> entities.
/// </summary>
public interface IAuditEntryRepository : IRepositoryQueryable<int, IAuditEntry>
{
/// <summary>
/// Gets a page of entries.
/// </summary>
IEnumerable<IAuditEntry> GetPage(long pageIndex, int pageCount, out long records);
/// <summary>
/// Determines whether the repository is available.
/// </summary>
/// <remarks>During an upgrade, the repository may not be available, until the table has been created.</remarks>
bool IsAvailable();
}
}
@@ -0,0 +1,15 @@
using Umbraco.Core.Models;
namespace Umbraco.Core.Persistence.Repositories
{
/// <summary>
/// Represents a repository for <see cref="IConsent"/> entities.
/// </summary>
public interface IConsentRepository : IRepositoryQueryable<int, IConsent>
{
/// <summary>
/// Clears the current flag.
/// </summary>
void ClearCurrent(string source, string context, string action);
}
}
@@ -39,5 +39,7 @@ namespace Umbraco.Core.Persistence.Repositories
void AssignRoles(int[] memberIds, string[] roleNames);
void DissociateRoles(int[] memberIds, string[] roleNames);
int[] GetMemberIds(string[] names);
}
}
}
@@ -183,7 +183,7 @@ namespace Umbraco.Core.Persistence.Repositories
}
public IEnumerable<IMemberGroup> GetMemberGroupsForMember(string username)
{
{
var sql = new Sql()
.Select("un.*")
.From("umbracoNode AS un")
@@ -192,16 +192,15 @@ namespace Umbraco.Core.Persistence.Repositories
.InnerJoin("cmsMember")
.On("cmsMember.nodeId = cmsMember2MemberGroup.Member")
.Where("un.nodeObjectType=@objectType", new { objectType = NodeObjectTypeId })
.Where("cmsMember.LoginName=@loginName", new { loginName = username });
.Where("cmsMember.LoginName=@loginName", new { loginName = username });
return Database.Fetch<NodeDto>(sql)
.DistinctBy(dto => dto.NodeId)
.Select(x => _modelFactory.BuildEntity(x));
}
public void AssignRoles(string[] usernames, string[] roleNames)
public int[] GetMemberIds(string[] names)
{
//first get the member ids based on the usernames
var memberSql = new Sql();
var memberObjectType = new Guid(Constants.ObjectTypes.Member);
memberSql.Select("umbracoNode.id")
@@ -209,25 +208,19 @@ namespace Umbraco.Core.Persistence.Repositories
.InnerJoin<MemberDto>()
.On<NodeDto, MemberDto>(dto => dto.NodeId, dto => dto.NodeId)
.Where<NodeDto>(x => x.NodeObjectType == memberObjectType)
.Where("cmsMember.LoginName in (@usernames)", new { usernames = usernames });
var memberIds = Database.Fetch<int>(memberSql).ToArray();
.Where("cmsMember.LoginName in (@names)", new { names });
return Database.Fetch<int>(memberSql).ToArray();
}
public void AssignRoles(string[] usernames, string[] roleNames)
{
var memberIds = GetMemberIds(usernames);
AssignRolesInternal(memberIds, roleNames);
}
public void DissociateRoles(string[] usernames, string[] roleNames)
{
//first get the member ids based on the usernames
var memberSql = new Sql();
var memberObjectType = new Guid(Constants.ObjectTypes.Member);
memberSql.Select("umbracoNode.id")
.From<NodeDto>()
.InnerJoin<MemberDto>()
.On<NodeDto, MemberDto>(dto => dto.NodeId, dto => dto.NodeId)
.Where<NodeDto>(x => x.NodeObjectType == memberObjectType)
.Where("cmsMember.LoginName in (@usernames)", new { usernames = usernames });
var memberIds = Database.Fetch<int>(memberSql).ToArray();
var memberIds = GetMemberIds(usernames);
DissociateRolesInternal(memberIds, roleNames);
}
@@ -337,4 +330,4 @@ namespace Umbraco.Core.Persistence.Repositories
/// </summary>
internal static event TypedEventHandler<IMemberGroupRepository, SaveEventArgs<IMemberGroup>> SavedMemberGroup;
}
}
}
@@ -90,7 +90,8 @@ namespace Umbraco.Core.Persistence.Repositories
sql.Select("umbracoNode.*", "cmsContentType.*", "cmsPropertyType.id AS PropertyTypeId", "cmsPropertyType.Alias",
"cmsPropertyType.Name", "cmsPropertyType.Description", "cmsPropertyType.mandatory", "cmsPropertyType.UniqueID",
"cmsPropertyType.validationRegExp", "cmsPropertyType.dataTypeId", "cmsPropertyType.sortOrder AS PropertyTypeSortOrder",
"cmsPropertyType.propertyTypeGroupId AS PropertyTypesGroupId", "cmsMemberType.memberCanEdit", "cmsMemberType.viewOnProfile",
"cmsPropertyType.propertyTypeGroupId AS PropertyTypesGroupId",
"cmsMemberType.memberCanEdit", "cmsMemberType.viewOnProfile", "cmsMemberType.isSensitive",
"cmsDataType.propertyEditorAlias", "cmsDataType.dbType", "cmsPropertyTypeGroup.id AS PropertyTypeGroupId",
"cmsPropertyTypeGroup.text AS PropertyGroupName", "cmsPropertyTypeGroup.uniqueID AS PropertyGroupUniqueID",
"cmsPropertyTypeGroup.sortorder AS PropertyGroupSortOrder", "cmsPropertyTypeGroup.contenttypeNodeId")
@@ -362,4 +363,4 @@ namespace Umbraco.Core.Persistence.Repositories
return Attempt<Guid>.Fail(propertyEditor);
}
}
}
}
@@ -308,6 +308,9 @@ namespace Umbraco.Core.Persistence.Repositories
public virtual void PersistNewItem(IEntity entity)
{
CachePolicy.Create((TEntity) entity, PersistNewItem);
//TODO: In v8 we should automatically reset dirty properties so they don't have to be manually reset in all of the implemented repositories
//if (entity is ICanBeDirty dirty) dirty.ResetDirtyProperties();
}
/// <summary>
@@ -317,6 +320,8 @@ namespace Umbraco.Core.Persistence.Repositories
public virtual void PersistUpdatedItem(IEntity entity)
{
CachePolicy.Update((TEntity) entity, PersistUpdatedItem);
//TODO: In v8 we should automatically reset dirty properties so they don't have to be manually reset in all of the implemented repositories
//if (entity is ICanBeDirty dirty) dirty.ResetDirtyProperties();
}
/// <summary>
@@ -343,4 +348,4 @@ namespace Umbraco.Core.Persistence.Repositories
UnitOfWork.DisposeIfDisposable();
}
}
}
}
@@ -285,7 +285,7 @@ umbracoUserGroup.userGroupName, umbracoUserGroup2App.app, umbracoUserGroup2App.u
protected override void PersistNewItem(IUserGroup entity)
{
((UserGroup)entity).AddingEntity();
((UserGroup)entity).AddingEntity();
var userGroupDto = UserGroupFactory.BuildDto(entity);
@@ -293,6 +293,8 @@ umbracoUserGroup.userGroupName, umbracoUserGroup2App.app, umbracoUserGroup2App.u
entity.Id = id;
PersistAllowedSections(entity);
entity.ResetDirtyProperties();
}
protected override void PersistUpdatedItem(IUserGroup entity)
@@ -304,6 +306,8 @@ umbracoUserGroup.userGroupName, umbracoUserGroup2App.app, umbracoUserGroup2App.u
Database.Update(userGroupDto);
PersistAllowedSections(entity);
entity.ResetDirtyProperties();
}
private void PersistAllowedSections(IUserGroup entity)
@@ -456,4 +460,4 @@ umbracoUserGroup.userGroupName, umbracoUserGroup2App.app, umbracoUserGroup2App.u
}
}
}
}
}
@@ -54,8 +54,8 @@ namespace Umbraco.Core.Persistence
//if the result is already a DeepCloneRuntimeCacheProvider then return it, otherwise
//wrap the result with a DeepCloneRuntimeCacheProvider
return cache is DeepCloneRuntimeCacheProvider
? cache
return cache is DeepCloneRuntimeCacheProvider
? cache
: new DeepCloneRuntimeCacheProvider(cache);
};
}
@@ -158,11 +158,11 @@ namespace Umbraco.Core.Persistence
CreateTemplateRepository(uow),
CreateTagRepository(uow),
_settings.Content)
{
{
//duplicates are allowed
EnsureUniqueNaming = false
};
}
}
public virtual IContentTypeRepository CreateContentTypeRepository(IScopeUnitOfWork uow)
{
@@ -319,13 +319,13 @@ namespace Umbraco.Core.Persistence
var userMembershipProvider = MembershipProviderExtensions.GetUsersMembershipProvider();
var passwordConfig = userMembershipProvider == null || userMembershipProvider.PasswordFormat != MembershipPasswordFormat.Hashed
? null
: new System.Collections.Generic.Dictionary<string, string> {{"hashAlgorithm", Membership.HashAlgorithmType}};
: new System.Collections.Generic.Dictionary<string, string> {{"hashAlgorithm", Membership.HashAlgorithmType}};
return new UserRepository(
uow,
//Need to cache users - we look up user information more than anything in the back office!
_cacheHelper,
_logger,
_logger,
_sqlSyntax,
passwordConfig);
}
@@ -397,5 +397,15 @@ namespace Umbraco.Core.Persistence
_logger,
_sqlSyntax);
}
public IConsentRepository CreateConsentRepository(IScopeUnitOfWork uow)
{
return new ConsentRepository(uow, _cacheHelper, _logger, _sqlSyntax);
}
public IAuditEntryRepository CreateAuditEntryRepository(IScopeUnitOfWork uow)
{
return new AuditEntryRepository(uow, _cacheHelper, _logger, _sqlSyntax);
}
}
}
@@ -21,7 +21,7 @@ namespace Umbraco.Core.PropertyEditors
public PropertyEditorResolver(IServiceProvider serviceProvider, ILogger logger, Func<IEnumerable<Type>> typeListProducerList, IRuntimeCacheProvider runtimeCache)
: base(serviceProvider, logger, typeListProducerList, ObjectLifetimeScope.Application)
{
var builder = new ManifestBuilder(
var builder = new ManifestBuilder(
runtimeCache,
new ManifestParser(new DirectoryInfo(IOHelper.MapPath("~/App_Plugins")), runtimeCache));
@@ -49,7 +49,10 @@ namespace Umbraco.Core.PropertyEditors
{
var nestedContentEditorFromPackage = editors.FirstOrDefault(x => x.Alias == "Our.Umbraco.NestedContent");
if (nestedContentEditorFromPackage != null)
{
nestedContentEditorFromPackage.Name = "(Obsolete) " + nestedContentEditorFromPackage.Name;
nestedContentEditorFromPackage.IsDeprecated = true;
}
return editors;
}
@@ -74,4 +77,4 @@ namespace Umbraco.Core.PropertyEditors
return PropertyEditors.SingleOrDefault(x => x.Alias == alias);
}
}
}
}
-3
View File
@@ -20,7 +20,6 @@ namespace Umbraco.Core.Scoping
public NoScope(ScopeProvider scopeProvider)
{
_scopeProvider = scopeProvider;
Timestamp = DateTime.Now;
#if DEBUG_SCOPES
_scopeProvider.RegisterScope(this);
#endif
@@ -29,8 +28,6 @@ namespace Umbraco.Core.Scoping
private readonly Guid _instanceId = Guid.NewGuid();
public Guid InstanceId { get { return _instanceId; } }
public DateTime Timestamp { get; }
/// <inheritdoc />
public bool CallContext { get { return false; } }
+1 -1
View File
@@ -374,7 +374,7 @@ namespace Umbraco.Core.Scoping
}
var parent = ParentScope;
_scopeProvider.AmbientScope = parent; // might be null = this is how scopes are removed from context objects
_scopeProvider.AmbientScope = parent;
#if DEBUG_SCOPES
_scopeProvider.Disposed(this);
+6 -51
View File
@@ -2,12 +2,10 @@
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Web;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
#if DEBUG_SCOPES
using System.Linq;
@@ -68,38 +66,22 @@ namespace Umbraco.Core.Scoping
// tests, any other things (see https://msdn.microsoft.com/en-us/library/dn458353(v=vs.110).aspx),
// but we don't want to make all of our objects serializable since they are *not* meant to be
// used in cross-AppDomain scenario anyways.
//
// in addition, whatever goes into the logical call context is serialized back and forth any
// time cross-AppDomain code executes, so if we put an "object" there, we'll get *another*
// "object" instance back - and so we cannot use a random object as a key.
//
// time cross-AppDomain code executes, so if we put an "object" there, we'll can *another*
// "object" instance - and so we cannot use a random object as a key.
// so what we do is: we register a guid in the call context, and we keep a table mapping those
// guids to the actual objects. the guid serializes back and forth without causing any issue,
// and we can retrieve the actual objects from the table.
// only issue: how are we supposed to clear the table? we can't, really. objects should take
// care of de-registering themselves from context.
// everything we use does, except the NoScope scope, which just stays there
//
// so far, the only objects that go into this table are scopes (using ScopeItemKey) and
// scope contexts (using ContextItemKey).
// during tests, NoScope can to into call context... nothing much we can do about it
private static readonly object StaticCallContextObjectsLock = new object();
private static readonly Dictionary<Guid, object> StaticCallContextObjects
= new Dictionary<Guid, object>();
// normal scopes and scope contexts take greate care removing themselves when disposed, so it
// is all safe. OTOH the NoScope *CANNOT* remove itself, this is by design, it *WILL* leak and
// there is little (nothing) we can do about it - NoScope exists for backward compatibility
// reasons and relying on it is greatly discouraged.
//
// however... we can *try* at protecting the app against memory leaks, by collecting NoScope
// instances that are too old. if anything actually *need* to retain a NoScope instance for
// a long time, it will break. but that's probably ok. so: the constants below define how
// long a NoScope instance can stay in the table before being removed, and how often we should
// collect the table - and collecting happens anytime SetCallContextObject is invoked
private static readonly TimeSpan StaticCallContextNoScopeLifeSpan = TimeSpan.FromMinutes(30);
private static readonly TimeSpan StaticCallContextCollectPeriod = TimeSpan.FromMinutes(4);
private static DateTime _staticCallContextLastCollect = DateTime.MinValue;
#if DEBUG_SCOPES
public Dictionary<Guid, object> CallContextObjects
{
@@ -174,7 +156,6 @@ namespace Umbraco.Core.Scoping
//Logging.LogHelper.Debug<ScopeProvider>("At:\r\n" + Head(Environment.StackTrace, 24));
#endif
StaticCallContextObjects.Remove(objectKey);
CollectStaticCallContextObjectsLocked();
}
}
else
@@ -190,37 +171,11 @@ namespace Umbraco.Core.Scoping
//Logging.LogHelper.Debug<ScopeProvider>("At:\r\n" + Head(Environment.StackTrace, 24));
#endif
StaticCallContextObjects.Add(objectKey, value);
CollectStaticCallContextObjectsLocked();
}
CallContext.LogicalSetData(key, objectKey);
}
}
private static void CollectStaticCallContextObjectsLocked()
{
// is it time to collect?
var now = DateTime.Now;
if (now - _staticCallContextLastCollect <= StaticCallContextCollectPeriod)
return;
// disable warning: this method is invoked from within a lock
// ReSharper disable InconsistentlySynchronizedField
var threshold = now.Add(-StaticCallContextNoScopeLifeSpan);
var guids = StaticCallContextObjects
.Where(x => x.Value is NoScope noScope && noScope.Timestamp < threshold)
.Select(x => x.Key)
.ToList();
if (guids.Count > 0)
{
LogHelper.Warn<ScopeProvider>($"Collected {guids.Count} NoScope instances from StaticCallContextObjects.");
foreach (var guid in guids)
StaticCallContextObjects.Remove(guid);
}
// ReSharper restore InconsistentlySynchronizedField
_staticCallContextLastCollect = now;
}
// this is for tests exclusively until we have a proper accessor in v8
internal static Func<IDictionary> HttpContextItemsGetter { get; set; }
@@ -420,6 +420,33 @@ namespace Umbraco.Core.Security
return await base.CheckPasswordAsync(user, password);
}
public override Task<IdentityResult> ResetPasswordAsync(int userId, string token, string newPassword)
{
var result = base.ResetPasswordAsync(userId, token, newPassword);
if (result.Result.Succeeded)
RaisePasswordResetEvent(userId);
return result;
}
/// <summary>
/// This is a special method that will reset the password but will raise the Password Changed event instead of the reset event
/// </summary>
/// <param name="userId"></param>
/// <param name="token"></param>
/// <param name="newPassword"></param>
/// <returns></returns>
/// <remarks>
/// We use this because in the back office the only way an admin can change another user's password without first knowing their password
/// is to generate a token and reset it, however, when we do this we want to track a password change, not a password reset
/// </remarks>
public Task<IdentityResult> ChangePasswordWithResetAsync(int userId, string token, string newPassword)
{
var result = base.ResetPasswordAsync(userId, token, newPassword);
if (result.Result.Succeeded)
RaisePasswordChangedEvent(userId);
return result;
}
public override Task<IdentityResult> ChangePasswordAsync(int userId, string currentPassword, string newPassword)
{
var result = base.ChangePasswordAsync(userId, currentPassword, newPassword);
@@ -554,27 +581,27 @@ namespace Umbraco.Core.Security
internal void RaiseAccountLockedEvent(int userId)
{
OnAccountLocked(new IdentityAuditEventArgs(AuditEvent.AccountLocked, GetCurrentRequestIpAddress(), userId));
OnAccountLocked(new IdentityAuditEventArgs(AuditEvent.AccountLocked, GetCurrentRequestIpAddress(), affectedUser: userId));
}
internal void RaiseAccountUnlockedEvent(int userId)
{
OnAccountUnlocked(new IdentityAuditEventArgs(AuditEvent.AccountUnlocked, GetCurrentRequestIpAddress(), userId));
OnAccountUnlocked(new IdentityAuditEventArgs(AuditEvent.AccountUnlocked, GetCurrentRequestIpAddress(), affectedUser: userId));
}
internal void RaiseForgotPasswordRequestedEvent(int userId)
{
OnForgotPasswordRequested(new IdentityAuditEventArgs(AuditEvent.ForgotPasswordRequested, GetCurrentRequestIpAddress(), userId));
OnForgotPasswordRequested(new IdentityAuditEventArgs(AuditEvent.ForgotPasswordRequested, GetCurrentRequestIpAddress(), affectedUser: userId));
}
internal void RaiseForgotPasswordChangedSuccessEvent(int userId)
{
OnForgotPasswordChangedSuccess(new IdentityAuditEventArgs(AuditEvent.ForgotPasswordChangedSuccess, GetCurrentRequestIpAddress(), userId));
OnForgotPasswordChangedSuccess(new IdentityAuditEventArgs(AuditEvent.ForgotPasswordChangedSuccess, GetCurrentRequestIpAddress(), affectedUser: userId));
}
internal void RaiseLoginFailedEvent(int userId)
{
OnLoginFailed(new IdentityAuditEventArgs(AuditEvent.LoginFailed, GetCurrentRequestIpAddress(), userId));
OnLoginFailed(new IdentityAuditEventArgs(AuditEvent.LoginFailed, GetCurrentRequestIpAddress(), affectedUser: userId));
}
internal void RaiseInvalidLoginAttemptEvent(string username)
@@ -584,31 +611,33 @@ namespace Umbraco.Core.Security
internal void RaiseLoginRequiresVerificationEvent(int userId)
{
OnLoginRequiresVerification(new IdentityAuditEventArgs(AuditEvent.LoginRequiresVerification, GetCurrentRequestIpAddress(), userId));
OnLoginRequiresVerification(new IdentityAuditEventArgs(AuditEvent.LoginRequiresVerification, GetCurrentRequestIpAddress(), affectedUser: userId));
}
internal void RaiseLoginSuccessEvent(int userId)
{
OnLoginSuccess(new IdentityAuditEventArgs(AuditEvent.LoginSucces, GetCurrentRequestIpAddress(), userId));
OnLoginSuccess(new IdentityAuditEventArgs(AuditEvent.LoginSucces, GetCurrentRequestIpAddress(), affectedUser: userId));
}
internal void RaiseLogoutSuccessEvent(int userId)
{
OnLogoutSuccess(new IdentityAuditEventArgs(AuditEvent.LogoutSuccess, GetCurrentRequestIpAddress(), userId));
OnLogoutSuccess(new IdentityAuditEventArgs(AuditEvent.LogoutSuccess, GetCurrentRequestIpAddress(), affectedUser: userId));
}
internal void RaisePasswordChangedEvent(int userId)
{
OnPasswordChanged(new IdentityAuditEventArgs(AuditEvent.PasswordChanged, GetCurrentRequestIpAddress(), userId));
OnPasswordChanged(new IdentityAuditEventArgs(AuditEvent.PasswordChanged, GetCurrentRequestIpAddress(), affectedUser: userId));
}
//TODO: I don't think this is required anymore since from 7.7 we no longer display the reset password checkbox since that didn't make sense.
internal void RaisePasswordResetEvent(int userId)
{
OnPasswordReset(new IdentityAuditEventArgs(AuditEvent.PasswordReset, GetCurrentRequestIpAddress(), userId));
OnPasswordReset(new IdentityAuditEventArgs(AuditEvent.PasswordReset, GetCurrentRequestIpAddress(), affectedUser: userId));
}
internal void RaiseResetAccessFailedCountEvent(int userId)
{
OnResetAccessFailedCount(new IdentityAuditEventArgs(AuditEvent.ResetAccessFailedCount, GetCurrentRequestIpAddress(), userId));
OnResetAccessFailedCount(new IdentityAuditEventArgs(AuditEvent.ResetAccessFailedCount, GetCurrentRequestIpAddress(), affectedUser: userId));
}
public static event EventHandler AccountLocked;
+92 -2
View File
@@ -4,6 +4,7 @@ using System.Linq;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
@@ -13,9 +14,13 @@ namespace Umbraco.Core.Services
{
public sealed class AuditService : ScopeRepositoryService, IAuditService
{
private readonly Lazy<bool> _isAvailable;
public AuditService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory)
: base(provider, repositoryFactory, logger, eventMessagesFactory)
{ }
{
_isAvailable = new Lazy<bool>(DetermineIsAvailable);
}
public void Add(AuditType type, string comment, int userId, int objectId)
{
@@ -107,5 +112,90 @@ namespace Umbraco.Core.Services
return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection, auditTypeFilter, customFilter);
}
}
/// <inheritdoc />
public IAuditEntry Write(int performingUserId, string perfomingDetails, string performingIp, DateTime eventDateUtc, int affectedUserId, string affectedDetails, string eventType, string eventDetails)
{
if (performingUserId < 0) throw new ArgumentOutOfRangeException(nameof(performingUserId));
if (string.IsNullOrWhiteSpace(perfomingDetails)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(perfomingDetails));
if (string.IsNullOrWhiteSpace(eventType)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(eventType));
if (string.IsNullOrWhiteSpace(eventDetails)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(eventDetails));
//we need to truncate the data else we'll get SQL errors
affectedDetails = affectedDetails?.Substring(0, Math.Min(affectedDetails.Length, AuditEntryDto.DetailsLength));
eventDetails = eventDetails.Substring(0, Math.Min(eventDetails.Length, AuditEntryDto.DetailsLength));
//validate the eventType - must contain a forward slash, no spaces, no special chars
var eventTypeParts = eventType.ToCharArray();
if (eventTypeParts.Contains('/') == false || eventTypeParts.All(c => char.IsLetterOrDigit(c) || c == '/' || c == '-') == false)
throw new ArgumentException(nameof(eventType) + " must contain only alphanumeric characters, hyphens and at least one '/' defining a category");
if (eventType.Length > AuditEntryDto.EventTypeLength)
throw new ArgumentException($"Must be max {AuditEntryDto.EventTypeLength} chars.", nameof(eventType));
if (performingIp != null && performingIp.Length > AuditEntryDto.IpLength)
throw new ArgumentException($"Must be max {AuditEntryDto.EventTypeLength} chars.", nameof(performingIp));
var entry = new AuditEntry
{
PerformingUserId = performingUserId,
PerformingDetails = perfomingDetails,
PerformingIp = performingIp,
EventDateUtc = eventDateUtc,
AffectedUserId = affectedUserId,
AffectedDetails = affectedDetails,
EventType = eventType,
EventDetails = eventDetails
};
if (_isAvailable.Value == false) return entry;
using (var uow = UowProvider.GetUnitOfWork())
{
var repository = RepositoryFactory.CreateAuditEntryRepository(uow);
repository.AddOrUpdate(entry);
uow.Commit();
}
return entry;
}
//TODO: Currently used in testing only, not part of the interface, need to add queryable methods to the interface instead
internal IEnumerable<IAuditEntry> GetAll()
{
if (_isAvailable.Value == false) return Enumerable.Empty<IAuditEntry>();
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
{
var repository = RepositoryFactory.CreateAuditEntryRepository(uow);
return repository.GetAll();
}
}
//TODO: Currently used in testing only, not part of the interface, need to add queryable methods to the interface instead
internal IEnumerable<IAuditEntry> GetPage(long pageIndex, int pageCount, out long records)
{
if (_isAvailable.Value == false)
{
records = 0;
return Enumerable.Empty<IAuditEntry>();
}
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
{
var repository = RepositoryFactory.CreateAuditEntryRepository(uow);
return repository.GetPage(pageIndex, pageCount, out records);
}
}
/// <summary>
/// Determines whether the repository is available.
/// </summary>
private bool DetermineIsAvailable()
{
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
{
var repository = RepositoryFactory.CreateAuditEntryRepository(uow);
return repository.IsAvailable();
}
}
}
}
}
@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Core.Services
{
/// <summary>
/// Implements <see cref="IContentService"/>.
/// </summary>
internal class ConsentService : ScopeRepositoryService, IConsentService
{
/// <summary>
/// Initializes a new instance of the <see cref="ContentService"/> class.
/// </summary>
public ConsentService(IScopeUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory)
: base(provider, repositoryFactory, logger, eventMessagesFactory)
{ }
/// <inheritdoc />
public IConsent RegisterConsent(string source, string context, string action, ConsentState state, string comment = null)
{
// prevent stupid states
var v = 0;
if ((state & ConsentState.Pending) > 0) v++;
if ((state & ConsentState.Granted) > 0) v++;
if ((state & ConsentState.Revoked) > 0) v++;
if (v != 1)
throw new ArgumentException("Invalid state.", nameof(state));
var consent = new Consent
{
Current = true,
Source = source,
Context = context,
Action = action,
CreateDate = DateTime.Now,
State = state,
Comment = comment
};
using (var uow = UowProvider.GetUnitOfWork())
{
var repository = RepositoryFactory.CreateConsentRepository(uow);
repository.ClearCurrent(source, context, action);
repository.AddOrUpdate(consent);
uow.Commit();
}
return consent;
}
/// <inheritdoc />
public IEnumerable<IConsent> LookupConsent(string source = null, string context = null, string action = null,
bool sourceStartsWith = false, bool contextStartsWith = false, bool actionStartsWith = false,
bool includeHistory = false)
{
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
{
var repository = RepositoryFactory.CreateConsentRepository(uow);
IQuery<IConsent> query = new Query<IConsent>();
if (string.IsNullOrWhiteSpace(source) == false)
query = sourceStartsWith ? query.Where(x => x.Source.StartsWith(source)) : query.Where(x => x.Source == source);
if (string.IsNullOrWhiteSpace(context) == false)
query = contextStartsWith ? query.Where(x => x.Context.StartsWith(context)) : query.Where(x => x.Context == context);
if (string.IsNullOrWhiteSpace(action) == false)
query = actionStartsWith ? query.Where(x => x.Action.StartsWith(action)) : query.Where(x => x.Action == action);
if (includeHistory == false)
query = query.Where(x => x.Current);
return repository.GetByQuery(query);
}
}
}
}
+26 -1
View File
@@ -1,10 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Services
{
/// <summary>
/// Represents a service for handling audit.
/// </summary>
public interface IAuditService : IService
{
void Add(AuditType type, string comment, int userId, int objectId);
@@ -54,5 +59,25 @@ namespace Umbraco.Core.Services
Direction orderDirection = Direction.Descending,
AuditType[] auditTypeFilter = null,
IQuery<IAuditItem> customFilter = null);
/// <summary>
/// Writes an audit entry for an audited event.
/// </summary>
/// <param name="performingUserId">The identifier of the user triggering the audited event.</param>
/// <param name="perfomingDetails">Free-form details about the user triggering the audited event.</param>
/// <param name="performingIp">The IP address or the request triggering the audited event.</param>
/// <param name="eventDateUtc">The date and time of the audited event.</param>
/// <param name="affectedUserId">The identifier of the user affected by the audited event.</param>
/// <param name="affectedDetails">Free-form details about the entity affected by the audited event.</param>
/// <param name="eventType">
/// The type of the audited event - must contain only alphanumeric chars and hyphens with forward slashes separating categories.
/// <example>
/// The eventType will generally be formatted like: {application}/{entity-type}/{category}/{sub-category}
/// Example: umbraco/user/sign-in/failed
/// </example>
/// </param>
/// <param name="eventDetails">Free-form details about the audited event.</param>
IAuditEntry Write(int performingUserId, string perfomingDetails, string performingIp, DateTime eventDateUtc, int affectedUserId, string affectedDetails, string eventType, string eventDetails);
}
}
}
@@ -0,0 +1,45 @@
using System.Collections.Generic;
using Umbraco.Core.Models;
namespace Umbraco.Core.Services
{
/// <summary>
/// A service for handling lawful data processing requirements
/// </summary>
/// <remarks>
/// <para>Consent can be given or revoked or changed via the <see cref="RegisterConsent"/> method, which
/// creates a new <see cref="IConsent"/> entity to track the consent. Revoking a consent is performed by
/// registering a revoked consent.</para>
/// <para>A consent can be revoked, by registering a revoked consent, but cannot be deleted.</para>
/// <para>Getter methods return the current state of a consent, i.e. the latest <see cref="IConsent"/>
/// entity that was created.</para>
/// </remarks>
public interface IConsentService : IService
{
/// <summary>
/// Registers consent.
/// </summary>
/// <param name="source">The source, i.e. whoever is consenting.</param>
/// <param name="context"></param>
/// <param name="action"></param>
/// <param name="state">The state of the consent.</param>
/// <param name="comment">Additional free text.</param>
/// <returns>The corresponding consent entity.</returns>
IConsent RegisterConsent(string source, string context, string action, ConsentState state, string comment = null);
/// <summary>
/// Retrieves consents.
/// </summary>
/// <param name="source">The optional source.</param>
/// <param name="context">The optional context.</param>
/// <param name="action">The optional action.</param>
/// <param name="sourceStartsWith">Determines whether <paramref name="source"/> is a start pattern.</param>
/// <param name="contextStartsWith">Determines whether <paramref name="context"/> is a start pattern.</param>
/// <param name="actionStartsWith">Determines whether <paramref name="action"/> is a start pattern.</param>
/// <param name="includeHistory">Determines whether to include the history of consents.</param>
/// <returns>Consents matching the paramters.</returns>
IEnumerable<IConsent> LookupConsent(string source = null, string context = null, string action = null,
bool sourceStartsWith = false, bool contextStartsWith = false, bool actionStartsWith = false,
bool includeHistory = false);
}
}
+3 -2
View File
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net.Http;
using System.Xml.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
@@ -198,7 +199,7 @@ namespace Umbraco.Core.Services
/// </summary>
/// <param name="memberTypeId">Id of the MemberType</param>
void DeleteMembersOfType(int memberTypeId);
[Obsolete("Use the overload with 'long' parameter types instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
IEnumerable<IMember> FindMembersByDisplayName(string displayNameToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
@@ -249,4 +250,4 @@ namespace Umbraco.Core.Services
/// <returns><see cref="IEnumerable{IMember}"/></returns>
IEnumerable<IMember> GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact);
}
}
}
+104 -14
View File
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Threading;
using System.Web.Security;
using System.Xml.Linq;
@@ -14,6 +15,9 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.UnitOfWork;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using Umbraco.Core.Security;
namespace Umbraco.Core.Services
@@ -25,11 +29,11 @@ namespace Umbraco.Core.Services
{
private readonly IMemberGroupService _memberGroupService;
private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer();
private readonly IDataTypeService _dataTypeService;
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
//only for unit tests!
internal MembershipProviderBase MembershipProvider { get; set; }
private readonly IDataTypeService _dataTypeService;
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
//only for unit tests!
internal MembershipProviderBase MembershipProvider { get; set; }
public MemberService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService, IDataTypeService dataTypeService)
: base(provider, repositoryFactory, logger, eventMessagesFactory)
@@ -38,7 +42,7 @@ namespace Umbraco.Core.Services
if (dataTypeService == null) throw new ArgumentNullException("dataTypeService");
_memberGroupService = memberGroupService;
_dataTypeService = dataTypeService;
}
}
#region IMemberService Implementation
@@ -94,7 +98,7 @@ namespace Umbraco.Core.Services
{
if (member == null) throw new ArgumentNullException("member");
var provider = MembershipProvider ?? MembershipProviderExtensions.GetMembersMembershipProvider();
var provider = MembershipProvider ?? MembershipProviderExtensions.GetMembersMembershipProvider();
if (provider.IsUmbracoMembershipProvider())
{
provider.ChangePassword(member.Username, "", password);
@@ -588,7 +592,7 @@ namespace Umbraco.Core.Services
Mandate.ParameterCondition(pageIndex >= 0, "pageIndex");
Mandate.ParameterCondition(pageSize > 0, "pageSize");
using (var uow = UowProvider.GetUnitOfWork(readOnly:true))
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
{
var repository = RepositoryFactory.CreateMemberRepository(uow);
return repository.GetPagedXmlEntriesByPath("-1", pageIndex, pageSize, null, out totalRecords);
@@ -986,10 +990,10 @@ namespace Umbraco.Core.Services
/// <param name="raiseEvents">Optional parameter to raise events.
/// Default is <c>True</c> otherwise set to <c>False</c> to not raise events</param>
public void Save(IMember entity, bool raiseEvents = true)
{
{
//trimming username and email to make sure we have no trailing space
entity.Username = entity.Username.Trim();
entity.Email = entity.Email.Trim();
entity.Email = entity.Email.Trim();
using (var uow = UowProvider.GetUnitOfWork())
{
@@ -1066,7 +1070,7 @@ namespace Umbraco.Core.Services
if (raiseEvents)
{
saveEventArgs.CanCancel = false;
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs);
}
}
@@ -1173,12 +1177,16 @@ namespace Umbraco.Core.Services
public void AssignRoles(string[] usernames, string[] roleNames)
{
int[] memberIds;
using (var uow = UowProvider.GetUnitOfWork())
{
var repository = RepositoryFactory.CreateMemberGroupRepository(uow);
repository.AssignRoles(usernames, roleNames);
memberIds = repository.GetMemberIds(usernames);
repository.AssignRoles(memberIds, roleNames);
uow.Commit();
}
AssignedRoles?.Invoke(this, new RolesEventArgs(memberIds, roleNames));
}
public void DissociateRole(string username, string roleName)
@@ -1188,12 +1196,16 @@ namespace Umbraco.Core.Services
public void DissociateRoles(string[] usernames, string[] roleNames)
{
int[] memberIds;
using (var uow = UowProvider.GetUnitOfWork())
{
var repository = RepositoryFactory.CreateMemberGroupRepository(uow);
repository.DissociateRoles(usernames, roleNames);
memberIds = repository.GetMemberIds(usernames);
repository.DissociateRoles(memberIds, roleNames);
uow.Commit();
}
RemovedRoles?.Invoke(this, new RolesEventArgs(memberIds, roleNames));
}
public void AssignRole(int memberId, string roleName)
@@ -1209,6 +1221,8 @@ namespace Umbraco.Core.Services
repository.AssignRoles(memberIds, roleNames);
uow.Commit();
}
AssignedRoles?.Invoke(this, new RolesEventArgs(memberIds, roleNames));
}
public void DissociateRole(int memberId, string roleName)
@@ -1224,12 +1238,83 @@ namespace Umbraco.Core.Services
repository.DissociateRoles(memberIds, roleNames);
uow.Commit();
}
RemovedRoles?.Invoke(this, new RolesEventArgs(memberIds, roleNames));
}
#endregion
/// <summary>
/// Used to export a member
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
/// <remarks>
/// This is internal for now and is used to export a member in the member editor, it will raise an event so that auditing
/// logs can be created
/// </remarks>
internal MemberExportModel ExportMember(Guid key)
{
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
{
var repository = RepositoryFactory.CreateMemberRepository(uow);
var query = Query<IMember>.Builder.Where(x => x.Key == key);
var member = repository.GetByQuery(query).FirstOrDefault();
if (member == null) return null;
var model = new MemberExportModel
{
Id = member.Id,
Key = member.Key,
Name = member.Name,
Username = member.Username,
Email = member.Email,
Groups = GetAllRoles(member.Id).ToList(),
ContentTypeAlias = member.ContentTypeAlias,
CreateDate = member.CreateDate,
UpdateDate = member.UpdateDate,
Properties = new List<MemberExportProperty>(GetPropertyExportItems(member))
};
uow.Events.Dispatch(Exported, this, new ExportedMemberEventArgs(member, model));
return model;
}
}
private static IEnumerable<MemberExportProperty> GetPropertyExportItems(IMember member)
{
if (member == null) throw new ArgumentNullException(nameof(member));
var exportProperties = new List<MemberExportProperty>();
foreach (var property in member.Properties)
{
//ignore list
switch (property.Alias)
{
case Constants.Conventions.Member.PasswordQuestion:
continue;
}
var propertyExportModel = new MemberExportProperty
{
Id = property.Id,
Alias = property.Alias,
Name = property.PropertyType.Name,
Value = property.Value,
CreateDate = property.CreateDate,
UpdateDate = property.UpdateDate
};
exportProperties.Add(propertyExportModel);
}
return exportProperties;
}
private IMemberType FindMemberTypeByAlias(string memberTypeAlias)
{
using (var uow = UowProvider.GetUnitOfWork())
@@ -1289,6 +1374,11 @@ namespace Umbraco.Core.Services
/// </summary>
public static event TypedEventHandler<IMemberService, SaveEventArgs<IMember>> Saved;
public static event TypedEventHandler<IMemberService, RolesEventArgs> AssignedRoles;
public static event TypedEventHandler<IMemberService, RolesEventArgs> RemovedRoles;
internal static event TypedEventHandler<IMemberService, ExportedMemberEventArgs> Exported;
#endregion
/// <summary>
@@ -1361,4 +1451,4 @@ namespace Umbraco.Core.Services
return member;
}
}
}
}
@@ -832,21 +832,21 @@ namespace Umbraco.Core.Services
foreach (var element in structureElement.Elements("DocumentType"))
{
var alias = element.Value;
if (_importedContentTypes.ContainsKey(alias))
{
var allowedChild = _importedContentTypes[alias];
if (allowedChild == null || allowedChildren.Any(x => x.Id.IsValueCreated && x.Id.Value == allowedChild.Id)) continue;
allowedChildren.Add(new ContentTypeSort(new Lazy<int>(() => allowedChild.Id), sortOrder, allowedChild.Alias));
sortOrder++;
}
else
var allowedChild = _importedContentTypes.ContainsKey(alias) ? _importedContentTypes[alias] : _contentTypeService.GetContentType(alias);
if (allowedChild == null)
{
_logger.Warn<PackagingService>(
string.Format(
"Packager: Error handling DocumentType structure. DocumentType with alias '{0}' could not be found and was not added to the structure for '{1}'.",
alias, contentType.Alias));
continue;
}
if (allowedChildren.Any(x => x.Id.IsValueCreated && x.Id.Value == allowedChild.Id)) continue;
allowedChildren.Add(new ContentTypeSort(new Lazy<int>(() => allowedChild.Id), sortOrder, allowedChild.Alias));
sortOrder++;
}
contentType.AllowedContentTypes = allowedChildren;
+13 -28
View File
@@ -62,38 +62,13 @@ namespace Umbraco.Core.Services
private Lazy<INotificationService> _notificationService;
private Lazy<IExternalLoginService> _externalLoginService;
private Lazy<IRedirectUrlService> _redirectUrlService;
private Lazy<IConsentService> _consentService;
internal IdkMap IdkMap { get; private set; }
/// <summary>
/// public ctor - will generally just be used for unit testing all items are optional and if not specified, the defaults will be used
/// </summary>
/// <param name="contentService"></param>
/// <param name="mediaService"></param>
/// <param name="contentTypeService"></param>
/// <param name="dataTypeService"></param>
/// <param name="fileService"></param>
/// <param name="localizationService"></param>
/// <param name="packagingService"></param>
/// <param name="entityService"></param>
/// <param name="relationService"></param>
/// <param name="memberGroupService"></param>
/// <param name="memberTypeService"></param>
/// <param name="memberService"></param>
/// <param name="userService"></param>
/// <param name="sectionService"></param>
/// <param name="treeService"></param>
/// <param name="tagService"></param>
/// <param name="notificationService"></param>
/// <param name="localizedTextService"></param>
/// <param name="auditService"></param>
/// <param name="domainService"></param>
/// <param name="taskService"></param>
/// <param name="macroService"></param>
/// <param name="publicAccessService"></param>
/// <param name="externalLoginService"></param>
/// <param name="migrationEntryService"></param>
/// <param name="redirectUrlService"></param>
public ServiceContext(
IContentService contentService = null,
IMediaService mediaService = null,
@@ -120,7 +95,8 @@ namespace Umbraco.Core.Services
IPublicAccessService publicAccessService = null,
IExternalLoginService externalLoginService = null,
IMigrationEntryService migrationEntryService = null,
IRedirectUrlService redirectUrlService = null)
IRedirectUrlService redirectUrlService = null,
IConsentService consentService = null)
{
if (migrationEntryService != null) _migrationEntryService = new Lazy<IMigrationEntryService>(() => migrationEntryService);
if (externalLoginService != null) _externalLoginService = new Lazy<IExternalLoginService>(() => externalLoginService);
@@ -148,6 +124,7 @@ namespace Umbraco.Core.Services
if (macroService != null) _macroService = new Lazy<IMacroService>(() => macroService);
if (publicAccessService != null) _publicAccessService = new Lazy<IPublicAccessService>(() => publicAccessService);
if (redirectUrlService != null) _redirectUrlService = new Lazy<IRedirectUrlService>(() => redirectUrlService);
if (consentService != null) _consentService = new Lazy<IConsentService>(() => consentService);
}
/// <summary>
@@ -310,6 +287,9 @@ namespace Umbraco.Core.Services
if (_redirectUrlService == null)
_redirectUrlService = new Lazy<IRedirectUrlService>(() => new RedirectUrlService(provider, repositoryFactory, logger, eventMessagesFactory));
if (_consentService == null)
_consentService = new Lazy<IConsentService>(() => new ConsentService(provider, repositoryFactory, logger, eventMessagesFactory));
}
internal IEventMessagesFactory EventMessagesFactory { get; private set; }
@@ -526,5 +506,10 @@ namespace Umbraco.Core.Services
{
get { return _redirectUrlService.Value; }
}
/// <summary>
/// Gets the <see cref="IConsentService"/> implementation.
/// </summary>
public IConsentService ConsentService => _consentService.Value;
}
}
}
+46 -19
View File
@@ -838,13 +838,9 @@ namespace Umbraco.Core.Services
repository.ReplaceGroupPermissions(groupId, permissions, entityIds);
uow.Commit();
uow.Events.Dispatch(UserGroupPermissionsAssigned, this, new SaveEventArgs<EntityPermission>(
entityIds.Select(
x => new EntityPermission(
groupId,
x,
permissions.Select(p => p.ToString(CultureInfo.InvariantCulture)).ToArray()))
.ToArray(), false));
var assigned = permissions.Select(p => p.ToString(CultureInfo.InvariantCulture)).ToArray();
uow.Events.Dispatch(UserGroupPermissionsAssigned, this,
new SaveEventArgs<EntityPermission>(entityIds.Select(x => new EntityPermission(groupId, x, assigned)).ToArray(), false));
}
}
@@ -865,13 +861,9 @@ namespace Umbraco.Core.Services
repository.AssignGroupPermission(groupId, permission, entityIds);
uow.Commit();
uow.Events.Dispatch(UserGroupPermissionsAssigned, this, new SaveEventArgs<EntityPermission>(
entityIds.Select(
x => new EntityPermission(
groupId,
x,
new[] { permission.ToString(CultureInfo.InvariantCulture) }))
.ToArray(), false));
var assigned = new[] { permission.ToString(CultureInfo.InvariantCulture) };
uow.Events.Dispatch(UserGroupPermissionsAssigned, this,
new SaveEventArgs<EntityPermission>(entityIds.Select(x => new EntityPermission(groupId, x, assigned)).ToArray(), false));
}
}
@@ -941,7 +933,7 @@ namespace Umbraco.Core.Services
/// </summary>
/// <param name="userGroup">UserGroup to save</param>
/// <param name="userIds">
/// If null than no changes are made to the users who are assigned to this group, however if a value is passed in
/// If null than no changes are made to the users who are assigned to this group, however if a value is passed in
/// than all users will be removed from this group and only these users will be added
/// </param>
/// <param name="raiseEvents">Optional parameter to raise events.
@@ -950,8 +942,28 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.GetUnitOfWork())
{
// we need to figure out which users have been added / removed, for audit purposes
var empty = new IUser[0];
var addedUsers = empty;
var removedUsers = empty;
if (userIds != null)
{
var urepository = RepositoryFactory.CreateUserRepository(uow);
var groupUsers = userGroup.HasIdentity ? urepository.GetAllInGroup(userGroup.Id).ToArray() : empty;
var xGroupUsers = groupUsers.ToDictionary(x => x.Id, x => x);
var groupIds = groupUsers.Select(x => x.Id).ToArray();
addedUsers = urepository.GetAll(userIds.Except(groupIds).ToArray()).Where(x => x.Id != 0).ToArray();
removedUsers = groupIds.Except(userIds).Select(x => xGroupUsers[x]).Where(x => x.Id != 0).ToArray();
}
//raise 2x events - the old and new one for backwards compat reasons
var saveEventArgs = new SaveEventArgs<IUserGroup>(userGroup);
if (raiseEvents && uow.Events.DispatchCancelable(SavingUserGroup, this, saveEventArgs))
var saveEventArgs2 = new SaveEventArgs<UserGroupWithUsers>(new UserGroupWithUsers(userGroup, addedUsers, removedUsers));
if (raiseEvents &&
(uow.Events.DispatchCancelable(SavingUserGroup, this, saveEventArgs) || uow.Events.DispatchCancelable(SavingUserGroup2, this, saveEventArgs2)))
{
uow.Commit();
return;
@@ -965,7 +977,10 @@ namespace Umbraco.Core.Services
if (raiseEvents)
{
saveEventArgs.CanCancel = false;
//raise 2x events - the old and new one for backwards compat reasons
uow.Events.Dispatch(SavedUserGroup, this, saveEventArgs);
uow.Events.Dispatch(SavedUserGroup2, this, saveEventArgs2);
}
}
}
@@ -1161,8 +1176,8 @@ namespace Umbraco.Core.Services
if (byGroup.Value.TryGetValue(pathId, out permissionsForNodeAndGroup) == false)
continue;
//In theory there will only be one EntityPermission in this group
// but there's nothing stopping the logic of this method
//In theory there will only be one EntityPermission in this group
// but there's nothing stopping the logic of this method
// from having more so we deal with it here
foreach (var entityPermission in permissionsForNodeAndGroup)
{
@@ -1198,7 +1213,7 @@ namespace Umbraco.Core.Services
/// Returns the resulting permission set for a group for the path based on all permissions provided for the branch
/// </summary>
/// <param name="pathPermissions">
/// The collective set of permissions provided to calculate the resulting permissions set for the path
/// The collective set of permissions provided to calculate the resulting permissions set for the path
/// based on a single group
/// </param>
/// <param name="pathIds">Must be ordered deepest to shallowest (right to left)</param>
@@ -1296,13 +1311,25 @@ namespace Umbraco.Core.Services
/// <summary>
/// Occurs before Save
/// </summary>
//TODO: In v8, change this event to be SaveEventArgs<UserGroupWithUsers> and remove SavingUserGroup2
public static event TypedEventHandler<IUserService, SaveEventArgs<IUserGroup>> SavingUserGroup;
/// <summary>
/// Occurs after Save
/// </summary>
//TODO: In v8, change this event to be SaveEventArgs<UserGroupWithUsers> and remove SavedUserGroup2
public static event TypedEventHandler<IUserService, SaveEventArgs<IUserGroup>> SavedUserGroup;
/// <summary>
/// Occurs after Save
/// </summary>
internal static event TypedEventHandler<IUserService, SaveEventArgs<UserGroupWithUsers>> SavingUserGroup2;
/// <summary>
/// Occurs after Save
/// </summary>
internal static event TypedEventHandler<IUserService, SaveEventArgs<UserGroupWithUsers>> SavedUserGroup2;
/// <summary>
/// Occurs before Delete
/// </summary>
+31 -1
View File
@@ -15,6 +15,7 @@
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
<TargetFrameworkProfile />
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -25,6 +26,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -137,6 +139,7 @@
<Compile Include="AsyncLock.cs" />
<Compile Include="Attempt{T}.cs" />
<Compile Include="Auditing\Audit.cs" />
<Compile Include="Auditing\AuditEventHandler.cs" />
<Compile Include="Auditing\AuditTypes.cs" />
<Compile Include="Auditing\IdentityAuditEventArgs.cs" />
<Compile Include="BindingRedirects.cs" />
@@ -352,7 +355,9 @@
<Compile Include="DisposableObjectSlim.cs" />
<Compile Include="EmailSender.cs" />
<Compile Include="Events\EventDefinitionFilter.cs" />
<Compile Include="Events\ExportedMemberEventArgs.cs" />
<Compile Include="Events\IDeletingMediaFilesEventArgs.cs" />
<Compile Include="Events\UserGroupWithUsers.cs" />
<Compile Include="Events\ScopeEventDispatcherBase.cs" />
<Compile Include="Events\ScopeLifespanMessagesFactory.cs" />
<Compile Include="Events\SendEmailEventArgs.cs" />
@@ -363,20 +368,30 @@
<Compile Include="IEmailSender.cs" />
<Compile Include="IHttpContextAccessor.cs" />
<Compile Include="Logging\RollingFileCleanupAppender.cs" />
<Compile Include="Models\AuditEntry.cs" />
<Compile Include="Models\Consent.cs" />
<Compile Include="Models\ConsentExtensions.cs" />
<Compile Include="Models\ConsentState.cs" />
<Compile Include="Models\EntityBase\EntityPath.cs" />
<Compile Include="Models\EntityBase\IDeletableEntity.cs" />
<Compile Include="Models\IAuditEntry.cs" />
<Compile Include="Models\IAuditItem.cs" />
<Compile Include="Models\IConsent.cs" />
<Compile Include="Models\IUserControl.cs" />
<Compile Include="Models\Membership\ContentPermissionSet.cs" />
<Compile Include="Models\Membership\EntityPermissionCollection.cs" />
<Compile Include="Models\Membership\IReadOnlyUserGroup.cs" />
<Compile Include="Models\Membership\IUserType.cs" />
<Compile Include="Models\Membership\MemberExportModel.cs" />
<Compile Include="Models\Membership\MemberExportProperty.cs" />
<Compile Include="Models\Membership\ReadOnlyUserGroup.cs" />
<Compile Include="Models\Membership\UserGroupExtensions.cs" />
<Compile Include="Models\Membership\UserProfile.cs" />
<Compile Include="Models\Membership\UserState.cs" />
<Compile Include="Models\Membership\UserType.cs" />
<Compile Include="Models\PublishedContent\PublishedContentTypeConverter.cs" />
<Compile Include="Models\Rdbms\AuditEntryDto.cs" />
<Compile Include="Models\Rdbms\ConsentDto.cs" />
<Compile Include="Models\Rdbms\MediaDto.cs" />
<Compile Include="Models\Rdbms\UserLoginDto.cs" />
<Compile Include="Models\Rdbms\UserStartNodeDto.cs" />
@@ -530,6 +545,7 @@
<Compile Include="Persistence\Constants-Locks.cs" />
<Compile Include="Persistence\DatabaseNodeLockExtensions.cs" />
<Compile Include="Persistence\DatabaseDebugHelper.cs" />
<Compile Include="Persistence\Factories\AuditEntryFactory.cs" />
<Compile Include="Persistence\Factories\ExternalLoginFactory.cs" />
<Compile Include="Persistence\Factories\MigrationEntryFactory.cs" />
<Compile Include="Persistence\Factories\PublicAccessEntryFactory.cs" />
@@ -540,7 +556,9 @@
<Compile Include="Persistence\LockedRepository.cs" />
<Compile Include="Persistence\LockingRepository.cs" />
<Compile Include="Persistence\Mappers\AccessMapper.cs" />
<Compile Include="Persistence\Mappers\AuditEntryMapper.cs" />
<Compile Include="Persistence\Mappers\AuditMapper.cs" />
<Compile Include="Persistence\Mappers\ConsentMapper.cs" />
<Compile Include="Persistence\Mappers\DomainMapper.cs" />
<Compile Include="Persistence\Mappers\ExternalLoginMapper.cs" />
<Compile Include="Persistence\Mappers\MigrationEntryMapper.cs" />
@@ -552,6 +570,10 @@
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenEightZero\AddInstructionCountColumn.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenEightZero\AddCmsMediaTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenEightZero\AddUserLoginTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenNineZero\AddUmbracoAuditTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenNineZero\AddUmbracoConsentTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenNineZero\AddIsSensitiveMemberTypeColumn.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenNineZero\CreateSensitiveDataUserGroup.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSevenZero\AddIndexToDictionaryKeyColumn.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSevenZero\EnsureContentTemplatePermissions.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSevenZero\ReduceDictionaryKeyColumnsSize.cs" />
@@ -622,12 +644,17 @@
<Compile Include="Persistence\Relators\AccessRulesRelator.cs" />
<Compile Include="Persistence\Relators\UserGroupRelator.cs" />
<Compile Include="Persistence\Relators\UserGroupSectionRelator.cs" />
<Compile Include="Persistence\Repositories\AuditEntryRepository.cs" />
<Compile Include="Persistence\Repositories\AuditRepository.cs" />
<Compile Include="Persistence\Repositories\BaseQueryType.cs" />
<Compile Include="Persistence\Factories\ConsentFactory.cs" />
<Compile Include="Persistence\Repositories\ConsentRepository.cs" />
<Compile Include="Persistence\Repositories\ContentBlueprintRepository.cs" />
<Compile Include="Persistence\Repositories\EntityContainerRepository.cs" />
<Compile Include="Persistence\Repositories\DomainRepository.cs" />
<Compile Include="Persistence\Repositories\ExternalLoginRepository.cs" />
<Compile Include="Persistence\Repositories\IAuditEntryRepository.cs" />
<Compile Include="Persistence\Repositories\IConsentRepository.cs" />
<Compile Include="Persistence\Repositories\Interfaces\IAuditRepository.cs" />
<Compile Include="Persistence\Repositories\Interfaces\IContentTypeCompositionRepository.cs" />
<Compile Include="Persistence\Repositories\Interfaces\IDomainRepository.cs" />
@@ -724,8 +751,10 @@
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\CreateCacheInstructionTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\MigrateStylesheetDataToFile.cs" />
<Compile Include="Services\AuditService.cs" />
<Compile Include="Services\ConsentService.cs" />
<Compile Include="Services\ContentTypeServiceExtensions.cs" />
<Compile Include="Models\RedirectUrl.cs" />
<Compile Include="Services\IConsentService.cs" />
<Compile Include="Services\IContentServiceBase.cs" />
<Compile Include="Services\IdkMap.cs" />
<Compile Include="Services\MediaServiceExtensions.cs" />
@@ -749,6 +778,7 @@
<Compile Include="Services\RepositoryService.cs" />
<Compile Include="Services\OperationStatus.cs" />
<Compile Include="Services\OperationStatusType.cs" />
<Compile Include="Events\RolesEventArgs.cs" />
<Compile Include="Services\ScopeRepositoryService.cs" />
<Compile Include="Services\TaskService.cs" />
<Compile Include="Strings\Css\StylesheetHelper.cs" />
@@ -1643,7 +1673,7 @@
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\AutoMapper.3.3.1\tools\AutoMapper.targets" Condition="Exists('..\packages\AutoMapper.3.3.1\tools\AutoMapper.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
@@ -1,2 +0,0 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp50</s:String></wpf:ResourceDictionary>
@@ -120,6 +120,7 @@ namespace Umbraco.Tests.Models.Mapping
Assert.AreEqual(propTypes.ElementAt(j).DataTypeId, result.PropertyTypes.ElementAt(j).DataTypeDefinitionId);
Assert.AreEqual(propTypes.ElementAt(j).MemberCanViewProperty, result.MemberCanViewProperty(result.PropertyTypes.ElementAt(j).Alias));
Assert.AreEqual(propTypes.ElementAt(j).MemberCanEditProperty, result.MemberCanEditProperty(result.PropertyTypes.ElementAt(j).Alias));
Assert.AreEqual(propTypes.ElementAt(j).IsSensitiveData, result.IsSensitiveProperty(result.PropertyTypes.ElementAt(j).Alias));
}
}
@@ -335,7 +336,7 @@ namespace Umbraco.Tests.Models.Mapping
.Returns(new[] { new TextboxPropertyEditor() });
var memberType = MockedContentTypes.CreateSimpleMemberType();
memberType.MemberTypePropertyTypes[memberType.PropertyTypes.Last().Alias] = new MemberTypePropertyProfileAccess(true, true);
memberType.MemberTypePropertyTypes[memberType.PropertyTypes.Last().Alias] = new MemberTypePropertyProfileAccess(true, true, true);
MockedContentTypes.EnsureAllIds(memberType, 8888);
@@ -539,6 +540,7 @@ namespace Umbraco.Tests.Models.Mapping
{
MemberCanEditProperty = true,
MemberCanViewProperty = true,
IsSensitiveData = true,
Id = 33,
SortOrder = 1,
Alias = "prop1",
@@ -556,6 +558,7 @@ namespace Umbraco.Tests.Models.Mapping
{
MemberCanViewProperty = false,
MemberCanEditProperty = false,
IsSensitiveData = false,
Id = 34,
SortOrder = 2,
Alias = "prop2",
@@ -944,6 +947,7 @@ namespace Umbraco.Tests.Models.Mapping
Label = "Prop 1",
MemberCanViewProperty = true,
MemberCanEditProperty = true,
IsSensitiveData = true,
Validation = new PropertyTypeValidation()
{
Mandatory = true,
@@ -963,6 +967,7 @@ namespace Umbraco.Tests.Models.Mapping
Assert.AreEqual(basic.Validation, result.Validation);
Assert.AreEqual(basic.MemberCanViewProperty, result.MemberCanViewProperty);
Assert.AreEqual(basic.MemberCanEditProperty, result.MemberCanEditProperty);
Assert.AreEqual(basic.IsSensitiveData, result.IsSensitiveData);
}
[Test]
@@ -1029,6 +1034,7 @@ namespace Umbraco.Tests.Models.Mapping
{
MemberCanEditProperty = true,
MemberCanViewProperty = true,
IsSensitiveData = true,
Alias = "property1",
Description = "this is property 1",
Inherited = false,
+2 -2
View File
@@ -277,7 +277,7 @@ namespace Umbraco.Tests
[Test]
public void ConvertToDateTimeTest()
{
var conv = "2016-06-07".TryConvertTo<DateTime?>();
var conv = "2016-06-07".TryConvertTo<DateTime>();
Assert.IsTrue(conv);
Assert.AreEqual(new DateTime(2016, 6, 7), conv.Result);
}
@@ -285,7 +285,7 @@ namespace Umbraco.Tests
[Test]
public void ConvertToNullableDateTimeTest()
{
var conv = "2016-06-07".TryConvertTo<DateTime>();
var conv = "2016-06-07".TryConvertTo<DateTime?>();
Assert.IsTrue(conv);
Assert.AreEqual(new DateTime(2016, 6, 7), conv.Result);
}
@@ -271,7 +271,7 @@ AnotherContentFinder
public void Resolves_Assigned_Mappers()
{
var foundTypes1 = _manager.ResolveAssignedMapperTypes();
Assert.AreEqual(29, foundTypes1.Count());
Assert.AreEqual(31, foundTypes1.Count());
}
[Test]
@@ -390,4 +390,4 @@ AnotherContentFinder
}
}
}
}
+3 -3
View File
@@ -79,8 +79,8 @@ namespace Umbraco.Tests.Plugins
var originalTypesFound = TypeFinderOriginal.FindClassesOfType<IApplicationStartupHandler>(_assemblies);
Assert.AreEqual(originalTypesFound.Count(), typesFound.Count());
Assert.AreEqual(9, typesFound.Count());
Assert.AreEqual(9, originalTypesFound.Count());
Assert.AreEqual(10, typesFound.Count());
Assert.AreEqual(10, originalTypesFound.Count());
}
[Test]
@@ -632,4 +632,4 @@ namespace Umbraco.Tests.Plugins
}
}
}
@@ -21,7 +21,7 @@ namespace Umbraco.Tests.PropertyEditors
[TestFixture]
public class ImageCropperTest
{
private const string cropperJson1 = "{\"focalPoint\": {\"left\": 0.96,\"top\": 0.80827067669172936},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\":\"thumb\",\"width\": 100,\"height\": 100,\"coordinates\": {\"x1\": 0.58729977382575338,\"y1\": 0.055768992440203169,\"x2\": 0,\"y2\": 0.32457553600198386}}]}";
private const string cropperJson1 = "{\"focalPoint\": {\"left\": 0.96,\"top\": 0.80827067669172936},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\":\"thumb\",\"width\": 100,\"height\": 100,\"coordinates\": {\"x1\": 0.58729977382575338,\"y1\": 0.055768992440203169,\"x2\": 0,\"y2\": 0.32457553600198386}}]}";
private const string cropperJson2 = "{\"focalPoint\": {\"left\": 0.98,\"top\": 0.80827067669172936},\"src\": \"/media/1005/img_0672.jpg\",\"crops\": [{\"alias\":\"thumb\",\"width\": 100,\"height\": 100,\"coordinates\": {\"x1\": 0.58729977382575338,\"y1\": 0.055768992440203169,\"x2\": 0,\"y2\": 0.32457553600198386}}]}";
private const string cropperJson3 = "{\"focalPoint\": {\"left\": 0.98,\"top\": 0.80827067669172936},\"src\": \"/media/1005/img_0672.jpg\",\"crops\": []}";
private const string mediaPath = "/media/1005/img_0671.jpg";
@@ -56,7 +56,7 @@ namespace Umbraco.Tests.PropertyEditors
index++;
}
Assert.AreEqual(index, 1);
Assert.AreEqual(index, 1);
}
[Test]
@@ -136,7 +136,7 @@ namespace Umbraco.Tests.PropertyEditors
}
[TestCase(cropperJson1, cropperJson1, true)]
[TestCase(cropperJson1, cropperJson2, false)]
[TestCase(cropperJson1, cropperJson2, false)]
public void CanConvertImageCropperPropertyEditor(string val1, string val2, bool expected)
{
try
@@ -266,7 +266,7 @@ namespace Umbraco.Tests.PropertyEditors
var urlStringPad = mediaPath.GetCropUrl(imageCropperValue: cropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Pad);
var urlStringMax = mediaPath.GetCropUrl(imageCropperValue: cropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Max);
var urlStringStretch = mediaPath.GetCropUrl(imageCropperValue: cropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Stretch);
Assert.AreEqual(mediaPath + "?mode=min&width=300&height=150", urlStringMin);
Assert.AreEqual(mediaPath + "?mode=boxpad&width=300&height=150", urlStringBoxPad);
Assert.AreEqual(mediaPath + "?mode=pad&width=300&height=150", urlStringPad);
@@ -380,4 +380,4 @@ namespace Umbraco.Tests.PropertyEditors
Assert.AreEqual(mediaPath + "?mode=pad&width=400&height=400&bgcolor=fff", urlString);
}
}
}
}
@@ -0,0 +1,56 @@
using System;
using System.Linq;
using NUnit.Framework;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Services;
using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests.Services
{
[TestFixture]
[DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerFixture)]
public class AuditServiceTests : BaseServiceTest
{
[Test]
public void CanCrudAuditEntry()
{
// fixme - why isn't this set by the test base class?
Database.Mapper = new PetaPocoMapper();
var yesterday = DateTime.UtcNow.AddDays(-1);
var entry = ServiceContext.AuditService.Write(123, "user 123, bob@example.com", null, yesterday, 456, "user 456, alice@example.com", "umbraco/user", "change property whatever value");
Assert.AreEqual(123, entry.PerformingUserId);
Assert.AreEqual("user 123, bob@example.com", entry.PerformingDetails);
Assert.AreEqual(yesterday, entry.EventDateUtc);
Assert.AreEqual(456, entry.AffectedUserId);
Assert.AreEqual("user 456, alice@example.com", entry.AffectedDetails);
Assert.AreEqual("umbraco/user", entry.EventType);
Assert.AreEqual("change property whatever value", entry.EventDetails);
var entries = ((AuditService)ServiceContext.AuditService).GetAll().ToArray();
Assert.IsNotNull(entries);
Assert.AreEqual(1, entries.Length);
Assert.AreEqual(123, entries[0].PerformingUserId);
for (var i = 0; i < 10; i++)
{
yesterday = yesterday.AddMinutes(1);
entry = ServiceContext.AuditService.Write(123 + i, "user 123, bob@example.com", null, yesterday, 456 + i, "user 456, alice@example.com", "umbraco/user", "change property whatever value");
}
//
// page 0 contains 123+9, 123+8
// page 1 contains 123+7, 123+6
// page 2 contains 123+5, 123+4
// ...
entries = ((AuditService)ServiceContext.AuditService).GetPage(2, 2, out var count).ToArray();
Assert.AreEqual(2, entries.Length);
Assert.AreEqual(123 + 5, entries[0].PerformingUserId);
Assert.AreEqual(123 + 4, entries[1].PerformingUserId);
}
}
}
@@ -0,0 +1,125 @@
using System;
using System.Linq;
using NUnit.Framework;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests.Services
{
[TestFixture]
[DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerFixture)]
public class ConsentServiceTests : BaseServiceTest
{
[SetUp]
public override void Initialize()
{
base.Initialize();
}
[TearDown]
public override void TearDown()
{
base.TearDown();
}
[Test]
public void CanCrudConsent()
{
// fixme - why isn't this set by the test base class?
Database.Mapper = new PetaPocoMapper();
var consentService = ServiceContext.ConsentService;
// can register
var consent = consentService.RegisterConsent("user/1234", "app1", "do-something", ConsentState.Granted, "no comment");
Assert.AreNotEqual(0, consent.Id);
Assert.IsTrue(consent.Current);
Assert.AreEqual("user/1234", consent.Source);
Assert.AreEqual("app1", consent.Context);
Assert.AreEqual("do-something", consent.Action);
Assert.AreEqual(ConsentState.Granted, consent.State);
Assert.AreEqual("no comment", consent.Comment);
Assert.IsTrue(consent.IsGranted());
// can register more
consentService.RegisterConsent("user/1234", "app1", "do-something-else", ConsentState.Granted, "no comment");
consentService.RegisterConsent("user/1236", "app1", "do-something", ConsentState.Granted, "no comment");
consentService.RegisterConsent("user/1237", "app2", "do-something", ConsentState.Granted, "no comment");
// can get by source
var consents = consentService.LookupConsent(source: "user/1235").ToArray();
Assert.IsEmpty(consents);
consents = consentService.LookupConsent(source: "user/1234").ToArray();
Assert.AreEqual(2, consents.Length);
Assert.IsTrue(consents.All(x => x.Source == "user/1234"));
Assert.IsTrue(consents.Any(x => x.Action == "do-something"));
Assert.IsTrue(consents.Any(x => x.Action == "do-something-else"));
// can get by context
consents = consentService.LookupConsent(context: "app3").ToArray();
Assert.IsEmpty(consents);
consents = consentService.LookupConsent(context: "app2").ToArray();
Assert.AreEqual(1, consents.Length);
consents = consentService.LookupConsent(context: "app1").ToArray();
Assert.AreEqual(3, consents.Length);
Assert.IsTrue(consents.Any(x => x.Action == "do-something"));
Assert.IsTrue(consents.Any(x => x.Action == "do-something-else"));
// can get by action
consents = consentService.LookupConsent(action: "do-whatever").ToArray();
Assert.IsEmpty(consents);
consents = consentService.LookupConsent(context: "app1", action: "do-something").ToArray();
Assert.AreEqual(2, consents.Length);
Assert.IsTrue(consents.All(x => x.Action == "do-something"));
Assert.IsTrue(consents.Any(x => x.Source == "user/1234"));
Assert.IsTrue(consents.Any(x => x.Source == "user/1236"));
// can revoke
consent = consentService.RegisterConsent("user/1234", "app1", "do-something", ConsentState.Revoked, "no comment");
consents = consentService.LookupConsent(source: "user/1234", context: "app1", action: "do-something").ToArray();
Assert.AreEqual(1, consents.Length);
Assert.IsTrue(consents[0].Current);
Assert.AreEqual(ConsentState.Revoked, consents[0].State);
// can filter
consents = consentService.LookupConsent(context: "app1", action: "do-", actionStartsWith: true).ToArray();
Assert.AreEqual(3, consents.Length);
Assert.IsTrue(consents.All(x => x.Context == "app1"));
Assert.IsTrue(consents.All(x => x.Action.StartsWith("do-")));
// can get history
consents = consentService.LookupConsent(source: "user/1234", context: "app1", action: "do-something", includeHistory: true).ToArray();
Assert.AreEqual(1, consents.Length);
Assert.IsTrue(consents[0].Current);
Assert.AreEqual(ConsentState.Revoked, consents[0].State);
Assert.IsTrue(consents[0].IsRevoked());
Assert.IsNotNull(consents[0].History);
var history = consents[0].History.ToArray();
Assert.AreEqual(1, history.Length);
Assert.IsFalse(history[0].Current);
Assert.AreEqual(ConsentState.Granted, history[0].State);
// cannot be stupid
Assert.Throws<ArgumentException>(() =>
consentService.RegisterConsent("user/1234", "app1", "do-something", ConsentState.Granted | ConsentState.Revoked, "no comment"));
}
}
}
@@ -108,8 +108,9 @@ namespace Umbraco.Tests.TestHelpers
var mappers = PluginManager.Current.FindAndCreateInstances<IMapperConfiguration>(
specificAssemblies: new[]
{
typeof(ContentModelMapper).Assembly,
typeof(ApplicationRegistrar).Assembly
typeof(ApplicationContext).Assembly, //Umbraco.Core
typeof(ContentModelMapper).Assembly, //Umbraco.Web
typeof(ApplicationRegistrar).Assembly //umbraco.businesslogic
});
foreach (var mapper in mappers)
{
@@ -218,4 +219,4 @@ namespace Umbraco.Tests.TestHelpers
protected CacheHelper CacheHelper { get; private set; }
}
}
}
@@ -47,6 +47,10 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
var mockedContentService = Mock.Of<IContentService>();
var mockedMediaService = Mock.Of<IMediaService>();
var mockedEntityService = Mock.Of<IEntityService>();
var mockedMemberService = Mock.Of<IMemberService>();
var mockedMemberTypeService = Mock.Of<IMemberTypeService>();
var mockedDataTypeService = Mock.Of<IDataTypeService>();
var mockedContentTypeService = Mock.Of<IContentTypeService>();
var mockedMigrationService = new Mock<IMigrationEntryService>();
//set it up to return anything so that the app ctx is 'Configured'
@@ -57,6 +61,10 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
contentService: mockedContentService,
mediaService: mockedMediaService,
entityService: mockedEntityService,
memberService: mockedMemberService,
memberTypeService: mockedMemberTypeService,
dataTypeService: mockedDataTypeService,
contentTypeService: mockedContentTypeService,
migrationEntryService: mockedMigrationService.Object,
localizedTextService:Mock.Of<ILocalizedTextService>(),
sectionService:Mock.Of<ISectionService>());
@@ -156,4 +164,4 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
protected abstract ApiController CreateController(Type controllerType, HttpRequestMessage msg, UmbracoHelper helper);
}
}
}
+3 -1
View File
@@ -31,6 +31,7 @@
<BootstrapperEnabled>true</BootstrapperEnabled>
<TargetFrameworkProfile />
<NugetPackages Condition="$(NugetPackages) == '' Or $(NugetPackages) == '*Undefined*'">$(SolutionDir)\packages</NugetPackages>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -41,7 +42,6 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<LangVersion>5</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -190,6 +190,8 @@
<Compile Include="Models\Mapping\UserModelMapperTests.cs" />
<Compile Include="Packaging\PackageExtractionTests.cs" />
<Compile Include="Persistence\Repositories\SimilarNodeNameTests.cs" />
<Compile Include="Services\AuditServiceTests.cs" />
<Compile Include="Services\ConsentServiceTests.cs" />
<Compile Include="TestHelpers\ControllerTesting\AuthenticateEverythingExtensions.cs" />
<Compile Include="TestHelpers\ControllerTesting\AuthenticateEverythingMiddleware.cs" />
<Compile Include="TestHelpers\ControllerTesting\SpecificAssemblyResolver.cs" />
+10 -1
View File
@@ -22,12 +22,21 @@ var packages = angular.module("umbraco.packages", []);
//module is initilized.
angular.module("umbraco.views", ["umbraco.viewcache"]);
angular.module("umbraco.viewcache", [])
.run(function($rootScope, $templateCache) {
.run(function ($rootScope, $templateCache, localStorageService) {
/** For debug mode, always clear template cache to cut down on
dev frustration and chrome cache on templates */
if (Umbraco.Sys.ServerVariables.isDebuggingEnabled) {
$templateCache.removeAll();
}
else {
var storedVersion = localStorageService.get("umbVersion");
if (!storedVersion || storedVersion !== Umbraco.Sys.ServerVariables.application.cacheBuster) {
//if the stored version doesn't match our cache bust version, clear the template cache
$templateCache.removeAll();
//store the current version
localStorageService.set("umbVersion", Umbraco.Sys.ServerVariables.application.cacheBuster);
}
}
})
.config([
//This ensures that all of our angular views are cache busted, if the path starts with views/ and ends with .html, then
@@ -115,7 +115,9 @@ angular.module("umbraco.directives")
toolbar: toolbar,
content_css: stylesheets,
style_formats: styleFormats,
autoresize_bottom_margin: 0
autoresize_bottom_margin: 0,
//see http://archive.tinymce.com/wiki.php/Configuration:cache_suffix
cache_suffix: "?umb__rnd=" + Umbraco.Sys.ServerVariables.application.cacheBuster
};
@@ -4,29 +4,32 @@
* @restrict E
**/
angular.module("umbraco.directives")
.directive('umbProperty', function (umbPropEditorHelper) {
.directive('umbProperty', function (umbPropEditorHelper, userService) {
return {
scope: {
property: "="
},
transclude: true,
restrict: 'E',
replace: true,
replace: true,
templateUrl: 'views/components/property/umb-property.html',
link: function(scope) {
scope.propertyAlias = Umbraco.Sys.ServerVariables.isDebuggingEnabled === true ? scope.property.alias : null;
link: function (scope) {
userService.getCurrentUser().then(function (u) {
var isAdmin = u.userGroups.indexOf('admin') !== -1;
scope.propertyAlias = (Umbraco.Sys.ServerVariables.isDebuggingEnabled === true || isAdmin) ? scope.property.alias : null;
});
},
//Define a controller for this directive to expose APIs to other directives
controller: function ($scope, $timeout) {
var self = this;
//set the API properties/methods
self.property = $scope.property;
self.setPropertyError = function(errorMsg) {
self.setPropertyError = function (errorMsg) {
$scope.property.propertyErrorMessage = errorMsg;
};
}
};
});
});
@@ -489,7 +489,7 @@
scope.editPropertyTypeSettings = function(property, group) {
if (!property.inherited && !property.locked) {
if (!property.inherited) {
scope.propertySettingsDialogModel = {};
scope.propertySettingsDialogModel.title = "Property settings";
@@ -547,6 +547,7 @@
property.validation.pattern = oldModel.property.validation.pattern;
property.showOnMemberProfile = oldModel.property.showOnMemberProfile;
property.memberCanEdit = oldModel.property.memberCanEdit;
property.isSensitiveValue = oldModel.property.isSensitiveValue;
// because we set state to active, to show a preview, we have to check if has been filled out
// label is required so if it is not filled we know it is a placeholder
@@ -493,7 +493,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
* @methodOf umbraco.resources.mediaResource
*
* @description
* Empties the media recycle bin
* Paginated search for media items starting on the supplied nodeId
*
* ##usage
* <pre>
@@ -506,7 +506,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
* @param {string} query The search query
* @param {int} pageNumber The page number
* @param {int} pageSize The number of media items on a page
* @param {int} searchFrom Id to search from
* @param {int} searchFrom NodeId to search from (-1 for root)
* @returns {Promise} resourcePromise object.
*
*/
@@ -10,8 +10,8 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
return umbRequestHelper.postSaveContent({
restApiUrl: umbRequestHelper.getApiUrl(
"memberApiBaseUrl",
"PostSave"),
"memberApiBaseUrl",
"PostSave"),
content: content,
action: action,
files: files,
@@ -22,8 +22,7 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
}
return {
getPagedResults: function (memberTypeAlias, options) {
getPagedResults: function(memberTypeAlias, options) {
if (memberTypeAlias === 'all-members') {
memberTypeAlias = null;
@@ -67,35 +66,35 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
}
var params = [
{ pageNumber: options.pageNumber },
{ pageSize: options.pageSize },
{ orderBy: options.orderBy },
{ orderDirection: options.orderDirection },
{ orderBySystemField: toBool(options.orderBySystemField) },
{ filter: options.filter }
{ pageNumber: options.pageNumber },
{ pageSize: options.pageSize },
{ orderBy: options.orderBy },
{ orderDirection: options.orderDirection },
{ orderBySystemField: toBool(options.orderBySystemField) },
{ filter: options.filter }
];
if (memberTypeAlias != null) {
params.push({ memberTypeAlias: memberTypeAlias });
}
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"memberApiBaseUrl",
"GetPagedResults",
params)),
'Failed to retrieve member paged result');
$http.get(
umbRequestHelper.getApiUrl(
"memberApiBaseUrl",
"GetPagedResults",
params)),
'Failed to retrieve member paged result');
},
getListNode: function (listName) {
getListNode: function(listName) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"memberApiBaseUrl",
"GetListNodeDisplay",
[{ listName: listName }])),
'Failed to retrieve data for member list ' + listName);
$http.get(
umbRequestHelper.getApiUrl(
"memberApiBaseUrl",
"GetListNodeDisplay",
[{ listName: listName }])),
'Failed to retrieve data for member list ' + listName);
},
/**
@@ -119,15 +118,15 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
* @returns {Promise} resourcePromise object containing the member item.
*
*/
getByKey: function (key) {
getByKey: function(key) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"memberApiBaseUrl",
"GetByKey",
[{ key: key }])),
'Failed to retrieve data for member id ' + key);
$http.get(
umbRequestHelper.getApiUrl(
"memberApiBaseUrl",
"GetByKey",
[{ key: key }])),
'Failed to retrieve data for member id ' + key);
},
/**
@@ -150,14 +149,14 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
* @returns {Promise} resourcePromise object.
*
*/
deleteByKey: function (key) {
deleteByKey: function(key) {
return umbRequestHelper.resourcePromise(
$http.post(
umbRequestHelper.getApiUrl(
"memberApiBaseUrl",
"DeleteByKey",
[{ key: key }])),
'Failed to delete item ' + key);
$http.post(
umbRequestHelper.getApiUrl(
"memberApiBaseUrl",
"DeleteByKey",
[{ key: key }])),
'Failed to delete item ' + key);
},
/**
@@ -190,24 +189,24 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
* @returns {Promise} resourcePromise object containing the member scaffold.
*
*/
getScaffold: function (alias) {
getScaffold: function(alias) {
if (alias) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"memberApiBaseUrl",
"GetEmpty",
[{ contentTypeAlias: alias }])),
'Failed to retrieve data for empty member item type ' + alias);
$http.get(
umbRequestHelper.getApiUrl(
"memberApiBaseUrl",
"GetEmpty",
[{ contentTypeAlias: alias }])),
'Failed to retrieve data for empty member item type ' + alias);
}
else {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"memberApiBaseUrl",
"GetEmpty")),
'Failed to retrieve data for empty member item type ' + alias);
$http.get(
umbRequestHelper.getApiUrl(
"memberApiBaseUrl",
"GetEmpty")),
'Failed to retrieve data for empty member item type ' + alias);
}
},
@@ -240,7 +239,7 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
* @returns {Promise} resourcePromise object containing the saved media item.
*
*/
save: function (member, isNew, files) {
save: function(member, isNew, files) {
return saveMember(member, "save" + (isNew ? "New" : ""), files);
}
};
@@ -8,9 +8,27 @@
* @description
* Defines the methods that are called when menu items declare only an action to execute
*/
function umbracoMenuActions($q, treeService, $location, navigationService, appState) {
function umbracoMenuActions($q, treeService, $location, navigationService, appState, umbRequestHelper, notificationsService, localizationService) {
return {
"ExportMember": function(args) {
var url = umbRequestHelper.getApiUrl(
"memberApiBaseUrl",
"ExportMemberData",
[{ key: args.entity.id }]);
umbRequestHelper.downloadFile(url).then(function() {
localizationService.localize("speechBubbles_memberExportedSuccess").then(function (value) {
notificationsService.success(value);
})
}, function(data) {
localizationService.localize("speechBubbles_memberExportedError").then(function (value) {
notificationsService.error(value);
})
});
},
/**
* @ngdoc method
@@ -74,4 +92,4 @@ function umbracoMenuActions($q, treeService, $location, navigationService, appSt
};
}
angular.module('umbraco.services').factory('umbracoMenuActions', umbracoMenuActions);
angular.module('umbraco.services').factory('umbracoMenuActions', umbracoMenuActions);
@@ -56,7 +56,7 @@
});
var saveProperties = _.map(realProperties, function (p) {
var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile');
var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile', 'isSensitiveData');
return saveProperty;
});
@@ -304,14 +304,14 @@
_.each(tab.properties, function (prop) {
//don't include the custom generic tab properties
if (!prop.alias.startsWith("_umb_")) {
//don't include a property that is marked readonly
if (!prop.alias.startsWith("_umb_") && !prop.readonly) {
saveModel.properties.push({
id: prop.id,
alias: prop.alias,
value: prop.value
});
}
});
});
@@ -338,4 +338,4 @@
}
angular.module('umbraco.services').factory('umbDataFormatter', umbDataFormatter);
})();
})();
@@ -333,6 +333,122 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ
failureCallback.apply(this, [data, status, headers, config]);
}
});
},
/**
* Downloads a file to the client using AJAX/XHR
* Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html
* See https://stackoverflow.com/a/24129082/694494
*/
downloadFile : function (httpPath) {
var deferred = $q.defer();
// Use an arraybuffer
$http.get(httpPath, { responseType: 'arraybuffer' })
.success(function (data, status, headers) {
var octetStreamMime = 'application/octet-stream';
var success = false;
// Get the headers
headers = headers();
// Get the filename from the x-filename header or default to "download.bin"
var filename = headers['x-filename'] || 'download.bin';
// Determine the content type from the header or default to "application/octet-stream"
var contentType = headers['content-type'] || octetStreamMime;
try {
// Try using msSaveBlob if supported
console.log("Trying saveBlob method ...");
var blob = new Blob([data], { type: contentType });
if (navigator.msSaveBlob)
navigator.msSaveBlob(blob, filename);
else {
// Try using other saveBlob implementations, if available
var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob;
if (saveBlob === undefined) throw "Not supported";
saveBlob(blob, filename);
}
console.log("saveBlob succeeded");
success = true;
} catch (ex) {
console.log("saveBlob method failed with the following exception:");
console.log(ex);
}
if (!success) {
// Get the blob url creator
var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL;
if (urlCreator) {
// Try to use a download link
var link = document.createElement('a');
if ('download' in link) {
// Try to simulate a click
try {
// Prepare a blob URL
console.log("Trying download link method with simulated click ...");
var blob = new Blob([data], { type: contentType });
var url = urlCreator.createObjectURL(blob);
link.setAttribute('href', url);
// Set the download attribute (Supported in Chrome 14+ / Firefox 20+)
link.setAttribute("download", filename);
// Simulate clicking the download link
var event = document.createEvent('MouseEvents');
event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
link.dispatchEvent(event);
console.log("Download link method with simulated click succeeded");
success = true;
} catch (ex) {
console.log("Download link method with simulated click failed with the following exception:");
console.log(ex);
}
}
if (!success) {
// Fallback to window.location method
try {
// Prepare a blob URL
// Use application/octet-stream when using window.location to force download
console.log("Trying download link method with window.location ...");
var blob = new Blob([data], { type: octetStreamMime });
var url = urlCreator.createObjectURL(blob);
window.location = url;
console.log("Download link method with window.location succeeded");
success = true;
} catch (ex) {
console.log("Download link method with window.location failed with the following exception:");
console.log(ex);
}
}
}
}
if (!success) {
// Fallback to window.open method
console.log("No methods worked for saving the arraybuffer, using last resort window.open");
window.open(httpPath, '_blank', '');
}
deferred.resolve();
})
.error(function (data, status) {
console.log("Request failed with status: " + status);
deferred.reject({
errorMsg: "An error occurred downloading the file",
data: data,
status: status
});
});
return deferred.promise;
}
};
}
@@ -1,7 +1,7 @@
angular.module("umbraco.install").controller("Umbraco.Install.UserController", function($scope, installerService) {
$scope.passwordPattern = /.*/;
$scope.installer.current.model.subscribeToNewsLetter = true;
$scope.installer.current.model.subscribeToNewsLetter = false;
if ($scope.installer.current.model.minNonAlphaNumericLength > 0) {
var exp = "";
@@ -426,103 +426,114 @@ input.umb-group-builder__group-title-input {
.content-type-editor-dialog.edit-property-settings {
.validation-wrapper {
position: relative;
}
.validation-label {
position: absolute;
top: 50%;
right: 0;
font-size: 12px;
color: @red;
transform: translate(0, -50%);
}
textarea.editor-label {
border-color:transparent;
box-shadow: none;
width: 100%;
box-sizing: border-box;
margin-bottom: 0;
font-size: 16px;
font-weight: bold;
resize: none;
line-height: 1.5em;
padding-left: 0;
border: none;
&:focus {
outline: none;
box-shadow: none !important;
.validation-wrapper {
position: relative;
}
}
.editor-placeholder {
border: 1px dashed @gray-8;
width: 100%;
height: 80px;
line-height: 80px;
text-align: center;
display: block;
border-radius: 5px;
color: @gray-3;
font-weight: bold;
font-size: 14px;
color: @turquoise-d1;
&:hover {
text-decoration: none;
}
}
.editor {
margin-bottom: 10px;
.editor-icon-wrapper {
border: 1px solid @gray-8;
width: 60px;
height: 60px;
text-align: center;
line-height: 60px;
border-radius: 5px;
float: left;
margin-right: 20px;
.icon {
font-size: 26px;
}
}
.editor-details {
float: left;
margin-top: 10px;
.editor-name {
display: block;
font-weight: bold;
}
.editor-editor {
display: block;
.validation-label {
position: absolute;
top: 50%;
right: 0;
font-size: 12px;
}
color: @red;
transform: translate(0, -50%);
}
.editor-settings-icon {
font-size: 18px;
margin-top: 8px;
}
}
.checkbox {
margin-bottom: 20px;
}
textarea.editor-label {
border-color: transparent;
box-shadow: none;
width: 100%;
box-sizing: border-box;
margin-bottom: 0;
font-size: 16px;
font-weight: bold;
resize: none;
line-height: 1.5em;
padding-left: 0;
border: none;
.editor-description,
.editor-validation-pattern {
min-width: 100%;
min-height: 25px;
resize: none;
box-sizing: border-box;
border: none;
overflow: hidden;
}
&:focus {
outline: none;
box-shadow: none !important;
}
}
.umb-dropdown {
width: 100%;
}
.editor-placeholder {
border: 1px dashed @gray-8;
width: 100%;
height: 80px;
line-height: 80px;
text-align: center;
display: block;
border-radius: 5px;
color: @gray-3;
font-weight: bold;
font-size: 14px;
color: @turquoise-d1;
&:hover {
text-decoration: none;
}
}
.editor {
margin-bottom: 10px;
.editor-icon-wrapper {
border: 1px solid @gray-8;
width: 60px;
height: 60px;
text-align: center;
line-height: 60px;
border-radius: 5px;
float: left;
margin-right: 20px;
.icon {
font-size: 26px;
}
}
.editor-details {
float: left;
margin-top: 10px;
.editor-name {
display: block;
font-weight: bold;
}
.editor-editor {
display: block;
font-size: 12px;
}
}
.editor-settings-icon {
font-size: 18px;
margin-top: 8px;
}
}
.checkbox {
margin-bottom: 20px;
}
.editor-description,
.editor-validation-pattern {
min-width: 100%;
min-height: 25px;
resize: none;
box-sizing: border-box;
border: none;
overflow: hidden;
}
.umb-dropdown {
width: 100%;
}
label.checkbox.no-indent {
width: 100%;
}
}
@@ -181,7 +181,7 @@
width: 640px !important;
}
.umb-modal i {
font-size: 14px;
font-size: 20px;
}
.umb-modal .breadcrumb {
background: none;
@@ -74,10 +74,6 @@
padding-right: 10px;
}
.login-overlay .btn-success {
padding: 12px 24px;
}
.login-overlay .form label {
font-weight: bold;
}

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