Compare commits
375 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 89c376f77a | |||
| 7c264b4da0 | |||
| 81d380a47e | |||
| 19e5715a84 | |||
| 2e2dce170a | |||
| 59f9a64e81 | |||
| 98b7467306 | |||
| 4bb75d73dd | |||
| dd8e43d80a | |||
| c9c9e8352a | |||
| c4e5ab54d2 | |||
| 53bfec8a04 | |||
| b4a0dadee9 | |||
| 014ac98d0b | |||
| 35a79a8598 | |||
| 3c4f052a27 | |||
| 97d7e3db84 | |||
| 0d2df4de81 | |||
| b361cfebb7 | |||
| ebea5e8170 | |||
| 995f3f38a3 | |||
| 2ad79956d9 | |||
| 96c6a17d03 | |||
| e2e0dd0c73 | |||
| 1a590664e7 | |||
| 64fd6ee7c5 | |||
| 2ed5b6c1ee | |||
| 41c4e024de | |||
| b46add81f0 | |||
| ff31cd9f72 | |||
| 084af43dab | |||
| edf7038848 | |||
| bcac02861d | |||
| 79ecaecc9e | |||
| f604117c6d | |||
| dcf7f35078 | |||
| 2aa0dfb2c5 | |||
| e4b77416a6 | |||
| 3aed889e2a | |||
| 2c6e7e46c2 | |||
| 0a71a09d21 | |||
| 0c868fb72d | |||
| a96cbce9b1 | |||
| e3e425695f | |||
| b539c6ec27 | |||
| 69f7623fb7 | |||
| 82820284f3 | |||
| 5f3090c81e | |||
| f66e239584 | |||
| a465ed415d | |||
| 5161fd0cef | |||
| eac9785c45 | |||
| 054a8b48fa | |||
| af0a03a3e0 | |||
| fb5e6c22ce | |||
| 6bc68005f4 | |||
| dfbc3c477a | |||
| 57426ae895 | |||
| 05c38db54c | |||
| 00fdace570 | |||
| 4e531976e9 | |||
| 0feb1b6cc0 | |||
| 830f460b7e | |||
| 3e2fa64820 | |||
| 7db4f841e3 | |||
| 509d27f93b | |||
| f42b122e73 | |||
| b4aad19573 | |||
| 1b6c070752 | |||
| bedce100d2 | |||
| 0e08a56ada | |||
| 03012cf54d | |||
| 7ff339e18a | |||
| 0048b9c4c8 | |||
| 47f5e6bf17 | |||
| a8908c90bc | |||
| 9e5e8b539a | |||
| 45d292e196 | |||
| 6bafd3a254 | |||
| 564074a2bd | |||
| 4161d244c1 | |||
| 171e0d87db | |||
| eea87e411f | |||
| 89edfe5782 | |||
| cbd5071eed | |||
| 3dbc2944aa | |||
| 709dab6afd | |||
| 8749b030cf | |||
| 62fd15d54b | |||
| 66e71db85c | |||
| 469527da0a | |||
| 304e574bbc | |||
| 723644ace5 | |||
| 2b936a6d31 | |||
| 3121635e07 | |||
| f2a3a269ba | |||
| 557fc1bcdd | |||
| 617bdb2bd1 | |||
| 2c91f9bf14 | |||
| 1a5fe63638 | |||
| dfb0efbc98 | |||
| d399ad6e51 | |||
| eb06e62937 | |||
| 728bf8b52d | |||
| ca74100e12 | |||
| cd63806895 | |||
| 3061dc4e6f | |||
| 5166dc08d6 | |||
| 05a9c44e9e | |||
| 8e0565e030 | |||
| ceb3ae361a | |||
| 4cee7b831a | |||
| f146262031 | |||
| ddae0d01e3 | |||
| b9ee85dabc | |||
| 29fa6061a9 | |||
| 8ee61312b6 | |||
| cfd4b8c7bf | |||
| 7ce3b6046c | |||
| 43d98c9682 | |||
| 62e4825a5e | |||
| 81d35c6079 | |||
| 7e15077f96 | |||
| 6543e3c058 | |||
| 7c73c379cf | |||
| 4bc9d96bce | |||
| 45fd54d046 | |||
| 4f062306a6 | |||
| 4a8730b925 | |||
| 0beda4b790 | |||
| d94e7cd906 | |||
| 213ea6470f | |||
| dde8ba5617 | |||
| e783308cf0 | |||
| 01f49bb07e | |||
| d06b7edd84 | |||
| 466de5ee0b | |||
| 976e1e8a35 | |||
| a3d08c19c5 | |||
| 23638702c8 | |||
| 761804baf8 | |||
| a633e45b44 | |||
| 78489237d9 | |||
| 8768947519 | |||
| 63b58dcd09 | |||
| ccfb643055 | |||
| 88e6e44f4e | |||
| 52684b6189 | |||
| 76a0c6c39f | |||
| 86bc50c61d | |||
| 13c675d718 | |||
| 924eab29af | |||
| 0ee3ebd180 | |||
| da0a1c808a | |||
| 9504eddf7e | |||
| e502db5f6f | |||
| 22820ae5db | |||
| b65dc25193 | |||
| 99b5b0a396 | |||
| 7c33b5b69b | |||
| 2ec66bf6d7 | |||
| 9421a92610 | |||
| 9c8c9d8a39 | |||
| 37d2203de1 | |||
| cec71f5651 | |||
| 7ee510ed38 | |||
| 136e9e66d2 | |||
| 55a2eb5b6c | |||
| 0c2c12fdd9 | |||
| ec427afe14 | |||
| 155a304ce3 | |||
| 161a19a82b | |||
| e40ed5e858 | |||
| 1ffbd734c8 | |||
| fd5bc49c87 | |||
| 2301a00cd1 | |||
| 313f7967cb | |||
| 6adaa1f70c | |||
| 8960b0828e | |||
| 397ab0e979 | |||
| 15c803b975 | |||
| d62b36ae5b | |||
| 34506664df | |||
| 2557b9a7fb | |||
| ac66dc9892 | |||
| 11e3d8c761 | |||
| 22b9941822 | |||
| b5b17a466b | |||
| 2c5b37a842 | |||
| ae864a3b19 | |||
| 449e464a14 | |||
| 5f72eb74db | |||
| fa85ce39c7 | |||
| bdea519770 | |||
| 78657f34d6 | |||
| e8fd7e2373 | |||
| 08965982fd | |||
| 732d3ef326 | |||
| 3da9fddd64 | |||
| e1821697d4 | |||
| a7756afae6 | |||
| fcd3bb2eee | |||
| 858156bab2 | |||
| cf5dac0ddd | |||
| 1cdf5c042e | |||
| 2281cd8291 | |||
| fa68f7fafe | |||
| 898e39dac5 | |||
| 2ae96f3810 | |||
| f6045a49b5 | |||
| 849cd4d315 | |||
| 10141bf4f6 | |||
| 11fcc8cd65 | |||
| 95aaf40281 | |||
| e25a39ec5d | |||
| c165a34ed7 | |||
| dc616bb6bf | |||
| cd6c7e51fd | |||
| 2caad3f2d0 | |||
| 4850b01097 | |||
| 92c0cfb9df | |||
| 8c5ee547b1 | |||
| b9c9a232cd | |||
| b565513052 | |||
| cff24e6ccf | |||
| 8ef3ecd714 | |||
| 7019c9433b | |||
| 1f66ebff1f | |||
| 47f8b483a9 | |||
| 1c56cd48ae | |||
| 6e9a9d6444 | |||
| c4850199f7 | |||
| 679333b9c9 | |||
| 02c609747d | |||
| 413e70db7d | |||
| c2cbac7ef9 | |||
| a4a079cd1b | |||
| 22fc9c617c | |||
| 0bcdfc8820 | |||
| b868c06e79 | |||
| f0dd32ea19 | |||
| 97607bc83b | |||
| 5b41262f41 | |||
| e8e1cd8323 | |||
| 1e17d6a527 | |||
| 0d05d0bf34 | |||
| 94184238a2 | |||
| f21d1f879e | |||
| 9c1b2499d5 | |||
| 60a315f964 | |||
| b37197f66a | |||
| 1870d5444d | |||
| a927d5fb84 | |||
| e433daf5ff | |||
| af476e1536 | |||
| fcfb9afc22 | |||
| 05818f6262 | |||
| accf8ba5f2 | |||
| 1788682af9 | |||
| a41833c137 | |||
| 7abc6d3d4f | |||
| a5f689385a | |||
| a551881ce2 | |||
| 4a5a8497b4 | |||
| 567c0977d0 | |||
| 9ec4247350 | |||
| 1607e51270 | |||
| 2f31175db5 | |||
| 044b43f6e1 | |||
| c2722d8120 | |||
| 325cefa349 | |||
| e43ecfd42c | |||
| 76725aa0d8 | |||
| d4cf4cd700 | |||
| 48c0b95707 | |||
| a5caf361e0 | |||
| 74c3aaade5 | |||
| cf94da2b8f | |||
| 826d6d5b15 | |||
| 56f4904fc0 | |||
| 9178e7afcf | |||
| 7f3733d4b5 | |||
| 3df23c3a34 | |||
| 7746c4a014 | |||
| b18945e630 | |||
| 4640cf56d1 | |||
| eae1ce7012 | |||
| e900711180 | |||
| 9f4875c8a7 | |||
| 46902d4335 | |||
| 5eb22b0fd9 | |||
| 6a18965946 | |||
| be1ec05072 | |||
| bb2caf85cb | |||
| 8ee8bda953 | |||
| b42f75df17 | |||
| caa3a3319f | |||
| 5caf95331a | |||
| 040e45ca70 | |||
| bd3d626147 | |||
| 47d08e69a4 | |||
| df182674b4 | |||
| 6ec9a2ae45 | |||
| bf00f7a663 | |||
| 74d8341620 | |||
| 70207e5374 | |||
| f4c038c64c | |||
| c4fa3aa649 | |||
| d51cda7843 | |||
| 372ffee42b | |||
| c1cfea7ebd | |||
| 3cdd8a0c43 | |||
| bf130584a6 | |||
| c3e67705ba | |||
| 7a80d689aa | |||
| eabc009f0c | |||
| d3a2d88347 | |||
| 93f4d65227 | |||
| a4c0342bda | |||
| 6c328d729d | |||
| 095633ba27 | |||
| 633d1bb0ae | |||
| fb89c3da3c | |||
| b2004057a5 | |||
| c59a226d56 | |||
| 0c25c36af0 | |||
| 29690df567 | |||
| 750600b202 | |||
| 61ce533a08 | |||
| 0a44ad3ab6 | |||
| 7c34255abe | |||
| d52216103b | |||
| a26cd625e7 | |||
| 1f22191bbc | |||
| 25088042a9 | |||
| 02397fd90c | |||
| e0ded863cc | |||
| bd55594be1 | |||
| 0e82696082 | |||
| a7a6854f9b | |||
| 23d225c14f | |||
| 1a289be999 | |||
| 8a0485e093 | |||
| 647492fbfb | |||
| fc43d3cdee | |||
| 971d7cf933 | |||
| 118a83e56c | |||
| 1f6dea7177 | |||
| f2667c3360 | |||
| f331736384 | |||
| a6bc94185c | |||
| 58533ca17e | |||
| 427e99dd29 | |||
| 1ec09e15cf | |||
| a48568d1ba | |||
| 5aa1b81cd4 | |||
| e1208e5019 | |||
| 995f644501 | |||
| f7eec8dafb | |||
| e039265fab | |||
| f08192a4ab | |||
| a16ac1f0ba | |||
| 469d6f9ca3 | |||
| 54cc80f4e8 | |||
| 64628c0a9d | |||
| 1e8bdc8f0c | |||
| b49db0e941 | |||
| 5678be0db9 | |||
| 464a227af1 | |||
| ae110c1cae | |||
| 3f1f67e323 | |||
| f1d37a5072 | |||
| 2dc6e395a8 | |||
| eedeb90673 | |||
| 977c390a95 |
@@ -113,6 +113,7 @@ build/ApiDocs/*
|
||||
build/ApiDocs/Output/*
|
||||
src/Umbraco.Web.UI.Client/bower_components/*
|
||||
/src/Umbraco.Web.UI/Umbraco/preview
|
||||
/src/Umbraco.Web.UI/Umbraco/preview.old
|
||||
|
||||
#Ignore Rule for output of generated documentation files from Grunt docserve
|
||||
src/Umbraco.Web.UI.Client/docs/api
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
# Code of Conduct
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
A primary goal of Umbraco CMS is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof).
|
||||
|
||||
This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior.
|
||||
|
||||
We invite all those who participate in Umbraco CMS to help us create safe and positive experiences for everyone.
|
||||
|
||||
## 2. Open Source Citizenship
|
||||
|
||||
A supplemental goal of this Code of Conduct is to increase open source citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community.
|
||||
|
||||
Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society.
|
||||
|
||||
If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know.
|
||||
|
||||
## 3. Expected Behavior
|
||||
|
||||
The following behaviors are expected and requested of all community members:
|
||||
|
||||
* Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community.
|
||||
* Exercise consideration and respect in your speech and actions.
|
||||
* Attempt collaboration before conflict.
|
||||
* Refrain from demeaning, discriminatory, or harassing behavior and speech.
|
||||
* Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential.
|
||||
* Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations.
|
||||
|
||||
## 4. Unacceptable Behavior
|
||||
|
||||
The following behaviors are considered harassment and are unacceptable within our community:
|
||||
|
||||
* Violence, threats of violence or violent language directed against another person.
|
||||
* Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language.
|
||||
* Posting or displaying sexually explicit or violent material.
|
||||
* Posting or threatening to post other people’s personally identifying information ("doxing").
|
||||
* Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability.
|
||||
* Inappropriate photography or recording.
|
||||
* Inappropriate physical contact. You should have someone’s consent before touching them.
|
||||
* Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances.
|
||||
* Deliberate intimidation, stalking or following (online or in person).
|
||||
* Advocating for, or encouraging, any of the above behavior.
|
||||
* Sustained disruption of community events, including talks and presentations.
|
||||
|
||||
## 5. Consequences of Unacceptable Behavior
|
||||
|
||||
Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated.
|
||||
|
||||
Anyone asked to stop unacceptable behavior is expected to comply immediately.
|
||||
|
||||
If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event).
|
||||
|
||||
## 6. Reporting Guidelines
|
||||
|
||||
If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. Please contact Sebastiaan Janssen - [sj@umbraco.dk](mailto:sj@umbraco.dk).
|
||||
|
||||
Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress.
|
||||
|
||||
## 7. Addressing Grievances
|
||||
|
||||
If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify Umbraco with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies.
|
||||
|
||||
## 8. Scope
|
||||
|
||||
We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues–online and in-person–as well as in all one-on-one communications pertaining to community business.
|
||||
|
||||
This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members.
|
||||
|
||||
## 9. Contact info
|
||||
|
||||
Sebastiaan Janssen - [sj@umbraco.dk](mailto:sj@umbraco.dk)
|
||||
|
||||
## 10. License and attribution
|
||||
|
||||
This Code of Conduct is distributed under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/).
|
||||
|
||||
Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy).
|
||||
|
||||
Retrieved on November 22, 2016 from [http://citizencodeofconduct.org/](http://citizencodeofconduct.org/)
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
# Contributing to Umbraco CMS
|
||||
|
||||
👍🎉 First off, thanks for taking the time to contribute! 🎉👍
|
||||
|
||||
The following is a set of guidelines for contributing to Umbraco CMS.
|
||||
|
||||
These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
|
||||
|
||||
Remember, we're a friendly bunch and are happy with whatever contribution you might provide. Below are guidelines for success that we've gathered over the years. If you choose to ignore them then we still love you 💖.
|
||||
|
||||
#### Table Of Contents
|
||||
|
||||
[Code of Conduct](#code-of-conduct)
|
||||
|
||||
[How Can I Contribute?](#how-can-i-contribute)
|
||||
* [Reporting Bugs](#reporting-bugs)
|
||||
* [Suggesting Enhancements](#suggesting-enhancements)
|
||||
* [Your First Code Contribution](#your-first-code-contribution)
|
||||
* [Pull Requests](#pull-requests)
|
||||
|
||||
[Styleguides](#styleguides)
|
||||
|
||||
[What should I know before I get started?](#what-should-i-know-before-i-get-started)
|
||||
* [Working with the source code](#working-with-the-source-code)
|
||||
* [What branch should I target for my contributions?](#what-branch-should-i-target-for-my-contributions)
|
||||
* [Building Umbraco from source code](#building-umbraco-from-source-code)
|
||||
* [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository)
|
||||
|
||||
[How do I even begin?](#how-do-i-even-begin)
|
||||
|
||||
[Problems?](#problems)
|
||||
|
||||
[Credits](#credits)
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project and everyone participating in it is governed by the [our Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [Sebastiaan Janssen - sj@umbraco.dk](mailto:sj@umbraco.dk).
|
||||
|
||||
## How Can I Contribute?
|
||||
|
||||
### Reporting Bugs
|
||||
This section guides you through submitting a bug report for Umbraco CMS. Following these guidelines helps maintainers and the community understand your report 📝, reproduce the behavior 💻 💻, and find related reports 🔎.
|
||||
|
||||
Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). Fill out [the required template](ISSUE_TEMPLATE.md), the information it asks for helps us resolve issues faster.
|
||||
|
||||
> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one.
|
||||
|
||||
##### Before Submitting A Bug Report
|
||||
|
||||
* Most importantly, check **if you can reproduce the problem** in the [latest version of Umbraco](https://our.umbraco.org/download/). We might have already fixed your particular problem.
|
||||
* It also helps tremendously to check if the issue you're experiencing is present in **a clean install** of the Umbraco version you're currently using. Custom code can have side-effects that don't occur in a clean install.
|
||||
* **Use the Google**. Whatever you're experiencing, Google it plus "Umbraco" - usually you can get some pretty good hints from the search results, including open issues and further troubleshooting hints.
|
||||
* If you do find and existing issue has **and the issue is still open**, add a comment to the existing issue if you have additional information. If you have the same problem and no new info to add, just "star" the issue.
|
||||
|
||||
Explain the problem and include additional details to help maintainers reproduce the problem. The following is a long description which we've boiled down into a few very simple question in the issue tracker when you create a new issue. We're listing the following hints to indicate that the most successful reports usually have a lot of this ground covered:
|
||||
|
||||
* **Use a clear and descriptive title** for the issue to identify the problem.
|
||||
* **Describe the exact steps which reproduce the problem** in as many details as possible. For example, start by explaining which steps you took in the backoffice to get to a certain undesireable result, e.g. you created a document type, inherting 3 levels deep, added a certain datatype, tried to save it and you got an error.
|
||||
* **Provide specific examples to demonstrate the steps**. If you wrote some code, try to provide a code sample as specific as possible to be able to reproduce the behavior.
|
||||
* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
|
||||
* **Explain which behavior you expected to see instead and why.**
|
||||
|
||||
Provide more context by answering these questions:
|
||||
|
||||
* **Can you reproduce the problem** when `debug="false"` in your `web.config` file?
|
||||
* **Did the problem start happening recently** (e.g. after updating to a new version of Umbraco) or was this always a problem?
|
||||
* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens.
|
||||
|
||||
Include details about your configuration and environment:
|
||||
|
||||
* **Which version of Umbraco are you using?**
|
||||
* **What is the environment you're using Umbraco in?** Is this a problem on your local machine or on a server. Tell us about your configuration: Windows version, IIS/IISExpress, database type, etc.
|
||||
* **Which packages do you have installed?**
|
||||
|
||||
### Suggesting Enhancements
|
||||
|
||||
This section guides you through submitting an enhancement suggestion for Atom, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion 📝 and find related suggestions 🔎.
|
||||
|
||||
Most of the suggestions in the [reporting bugs](#reporting-bugs) section also count for suggesting enhancements.
|
||||
|
||||
Some additional hints that may be helpful:
|
||||
|
||||
* **Include screenshots and animated GIFs** which help you demonstrate the steps or point out the part of Umbraco which the suggestion is related to.
|
||||
* **Explain why this enhancement would be useful to most Umbraco users** and isn't something that can or should be implemented as a [community package](https://our.umbraco.org/projects/).
|
||||
|
||||
### Your First Code Contribution
|
||||
|
||||
Unsure where to begin contributing to Umbraco? You can start by looking through [these `Up for grabs` and issues](http://issues.umbraco.org/issues/U4?q=%28project%3A+%7BU4%7D+Difficulty%3A+%7BVery+Easy%7D+%23Easy+%23Unresolved+Priority%3A+Normal+%23Major+%23Show-stopper+State%3A+-%7BIn+Progress%7D+sort+by%3A+votes+Affected+versions%3A+-6.*+Affected+versions%3A+-4.*%29+OR+%28tag%3A+%7BUp+For+Grabs%7D+%23Unresolved+%29).
|
||||
|
||||
The issue list is sorted by total number of upvotes. While not perfect, number of upvotes is a reasonable proxy for impact a given change will have.
|
||||
|
||||
### Pull Requests
|
||||
|
||||
The most successful pull requests usually look a like this:
|
||||
|
||||
* Fill in the required template
|
||||
* Include screenshots and animated GIFs in your pull request whenever possible.
|
||||
* Unit tests, while optional are awesome, thank you!
|
||||
* New code is commented with documentation from which [the reference documentation](https://our.umbraco.org/documentation/Reference/) is generated
|
||||
|
||||
Again, these are guidelines, not strict requirements.
|
||||
|
||||
## Styleguides
|
||||
|
||||
To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up.
|
||||
|
||||
That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc.
|
||||
|
||||
## What should I know before I get started?
|
||||
|
||||
### Working with the source code
|
||||
|
||||
Some parts of our source code is over 10 years old now. And when we say "old", we mean "mature" of course!
|
||||
|
||||
There's two big areas that you should know about:
|
||||
|
||||
1. The Umbraco backoffice is a extensible AngularJS app and requires you to run a `gulp dev` command while you're working with it, so changes are copied over to the appropriate directories and you can refresh your browser to view the results of your changes.
|
||||
You may need to run the following commands to set up gulp properly:
|
||||
```
|
||||
npm cache clean
|
||||
npm install -g bower
|
||||
npm install -g gulp
|
||||
npm install -g gulp-cli
|
||||
npm install
|
||||
gulp build
|
||||
```
|
||||
2. "The rest" is a C# based codebase, with some traces of our WebForms past but mostly ASP.NET MVC based these days. You can make changes, build them in Visual Studio, and hit `F5` to see the result.
|
||||
|
||||
To find the general areas of something you're looking to fix or improve, have a look at the following two parts of the API documentation.
|
||||
|
||||
* [The AngularJS based backoffice files](https://our.umbraco.org/apidocs/ui/#/api) (to be found in `src\Umbraco.Web.UI.Client\src`)
|
||||
* [The rest](https://our.umbraco.org/apidocs/csharp/)
|
||||
|
||||
### What branch should I target for my contributions?
|
||||
|
||||
We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-flow/), don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `dev-v7`. Whatever the default is, that's where we'd like you to target your contributions.
|
||||
|
||||

|
||||
|
||||
### Building Umbraco from source code
|
||||
|
||||
The easiest way to get started is to run `build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `gulp dev` in `src\Umbraco.Web.UI.Client`. See [this page](BUILD.md) for more details.
|
||||
|
||||
Alternatively, you can open `src\umbraco.sln` in Visual Studio 2017 ([the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects). In Visual Studio, find the Task Runner Explorer (in the View menu under Other Windows) and run the build task under the gulpfile.
|
||||
|
||||

|
||||
|
||||
After this build completes, you should be able to hit `F5` in Visual Studio to build and run the project. A IISExpress webserver will start and the Umbraco installer will pop up in your browser, follow the directions there to get a working Umbraco install up and running.
|
||||
|
||||
### Keeping your Umbraco fork in sync with the main repository
|
||||
|
||||
We recommend you sync with our repository before you submit your pull request. That way, you can fix any potential merge conflicts and make our lives a little bit easier.
|
||||
|
||||
Also, if you've submitted a pull request three weeks ago and want to work on something new, you'll want to get the latest code to build against of course.
|
||||
|
||||
To sync your fork with this original one, you'll have to add the upstream url, you only have to do this once:
|
||||
|
||||
```
|
||||
git remote add upstream https://github.com/umbraco/Umbraco-CMS.git
|
||||
```
|
||||
|
||||
Then when you want to get the changes from the main repository:
|
||||
|
||||
```
|
||||
git fetch upstream
|
||||
git rebase upstream/dev-v7
|
||||
```
|
||||
|
||||
In this command we're syncing with the `dev-v7` branch, but you can of course choose another one if needed.
|
||||
|
||||
(More info on how this works: [http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated](http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated))
|
||||
|
||||
## How do I even begin?
|
||||
|
||||
Great question! The short version goes like this:
|
||||
|
||||
* **Fork** - create a fork of [`Umbraco-CMS` on GitHub](https://github.com/umbraco/Umbraco-CMS)
|
||||
|
||||

|
||||
|
||||
* **Clone** - when GitHub has created your fork, you can clone it in your favorite Git tool
|
||||
|
||||

|
||||
|
||||
* **Build** - build your fork of Umbraco locally as described in [building Umbraco from source code](#building-umbraco-from-source-code)
|
||||
* **Change** - make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will happily give feedback
|
||||
* **Commit** - done? Yay! 🎉 It is recommended to create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-U4-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `U4-12345`
|
||||
* **Push** - great, now you can push the changes up to your fork on GitHub
|
||||
* **Create pull request** - exciting! You're ready to show us your changes (or not quite ready, you just need some feedback to progress). GitHub has picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go.
|
||||
|
||||

|
||||
|
||||
The Umbraco development team can now start reviewing your proposed changes and give you feedback on them. If it's not perfect, we'll either fix up what we need or we can request you to make some additional changes.
|
||||
|
||||
If you make the corrections we ask for in the same branch and push them to your fork again, the pull request automatically updates with the additional commit(s) so we can review it again. If all is well, we'll merge the code and your commits are forever part of Umbraco!
|
||||
|
||||
Not all changes are wanted so on occassion we might close a PR without merging it. We will give you feedback why we can't accept your changes at this and we'll be nice about it, thanking you for spending your valueable time.
|
||||
|
||||
Remember, if an issue is in the `Up for grabs` list or you've asked for some feedback before you send us a PR, your PR will not be closed as unwanted.
|
||||
|
||||
## Problems?
|
||||
|
||||
Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"](https://our.umbraco.org/forum/contributing-to-umbraco-cms/) forum, the team monitors that one closely!
|
||||
|
||||
## Credits
|
||||
|
||||
This contribution guide borrows heavily from the excellent work on [the Atom contribution guide](https://github.com/atom/atom/blob/master/CONTRIBUTING.md). A big [#h5yr](http://h5yr.com/) to them!
|
||||
@@ -0,0 +1,11 @@
|
||||
### Prerequisites
|
||||
|
||||
- [ ] I have written a descriptive pull-request title
|
||||
- [ ] I have linked this PR to an issue on the tracker at http://issues.umbraco.org
|
||||
|
||||
### Description
|
||||
<!-- A description of the changes proposed in the pull-request -->
|
||||
|
||||
|
||||
|
||||
<!-- Thanks for contributing to Umbraco CMS! -->
|
||||
@@ -6,21 +6,14 @@ The friendliest, most flexible and fastest growing ASP.NET CMS used by more than
|
||||
|
||||
[](https://vimeo.com/172382998/)
|
||||
|
||||
## Umbraco CMS ##
|
||||
## Umbraco CMS
|
||||
Umbraco is a free open source Content Management System built on the ASP.NET platform. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social.
|
||||
|
||||
|
||||
## Building Umbraco from source ##
|
||||
|
||||
The easiest way to get started is to run `build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `gulp dev` in `src\Umbraco.Web.UI.Client`. See [this page](BUILD.md) for more details.
|
||||
|
||||
Note that you can always [download a nightly build](http://nightly.umbraco.org/?container=umbraco-750) so you don't have to build the code yourself.
|
||||
|
||||
## Watch an introduction video ##
|
||||
## Watch an introduction video
|
||||
|
||||
[](https://umbraco.tv/videos/umbraco-v7/content-editor/basics/introduction/cms-explanation/)
|
||||
|
||||
## Umbraco - The Friendly CMS ##
|
||||
## Umbraco - The Friendly CMS
|
||||
|
||||
For the first time on the Microsoft platform, there is a free user and developer friendly CMS that makes it quick and easy to create websites - or a breeze to build complex web applications. Umbraco has award-winning integration capabilities and supports ASP.NET MVC or Web Forms, including User and Custom Controls, out of the box.
|
||||
|
||||
@@ -30,22 +23,24 @@ Used by more than 443,000 active websites including Carlsberg, Segway, Amazon an
|
||||
|
||||
To view more examples, please visit [https://umbraco.com/why-umbraco/#caseStudies](https://umbraco.com/why-umbraco/#caseStudies)
|
||||
|
||||
## Why Open Source? ##
|
||||
## Why Open Source?
|
||||
As an Open Source platform, Umbraco is more than just a CMS. We are transparent with our roadmap for future versions, our incremental sprint planning notes are publicly accessible and community contributions and packages are available for all to use.
|
||||
|
||||
## Downloading ##
|
||||
## Trying out Umbraco CMS
|
||||
|
||||
The downloadable Umbraco releases live at [https://our.umbraco.org/download](https://our.umbraco.org/download).
|
||||
[Umbraco Cloud](https://umbraco.com) is the easiest and fastest way to use Umbraco yet with full support for all your custom .NET code and intergrations. You're up and running in less than a minute and your life will be made easier with automated upgrades and a built-in deployment engine. We offer a free 14 day trial, no credit card needed.
|
||||
|
||||
## Forums ##
|
||||
If you want to DIY you can [download Umbraco](https://our.umbraco.org/download) either as a ZIP file or via NuGet. It's the same version of Umbraco CMS that powers Umbraco Cloud, but you'll need to find a place to host yourself and handling deployments and upgrades is all down to you.
|
||||
|
||||
Peer-to-peer support is available 24/7 at the community forum on [https://our.umbraco.org](https://our.umbraco.org).
|
||||
## Community
|
||||
|
||||
## Contribute to Umbraco ##
|
||||
Our friendly community is available 24/7 at the community hub we call ["Our Umbraco"](https://our.umbraco.org). Our Umbraco feature forums for questions and answers, documentation, downloadable plugins for Umbraco and a rich collection of community resources.
|
||||
|
||||
Umbraco is contribution focused and community driven. If you want to contribute back to Umbraco please check out our [guide to contributing](https://our.umbraco.org/contribute).
|
||||
## Contribute to Umbraco
|
||||
|
||||
## Found a bug? ##
|
||||
Umbraco is contribution focused and community driven. If you want to contribute back to Umbraco please check out our [guide to contributing](CONTRIBUTING.md).
|
||||
|
||||
## Found a bug?
|
||||
|
||||
Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](https://our.umbraco.org/contribute/report-an-issue-or-request-a-feature).
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ function Get-UmbracoBuildEnv
|
||||
# store tools in the module's directory
|
||||
# and cache them for two days
|
||||
$path = "$PSScriptRoot\temp"
|
||||
$src = "$PSScriptRoot\..\..\..\src"
|
||||
$cache = 2
|
||||
|
||||
if (-not (test-path $path))
|
||||
@@ -37,7 +38,7 @@ function Get-UmbracoBuildEnv
|
||||
if (-not (test-path $sevenZip))
|
||||
{
|
||||
Write-Host "Download 7-Zip..."
|
||||
&$nuget install 7-Zip.CommandLine -OutputDirectory $path -Verbosity quiet
|
||||
&$nuget install 7-Zip.CommandLine -configFile "$src\NuGet.config" -OutputDirectory $path -Verbosity quiet
|
||||
$dir = ls "$path\7-Zip.CommandLine.*" | sort -property Name -descending | select -first 1
|
||||
$file = ls -path "$dir" -name 7za.exe -recurse
|
||||
$file = ls -path "$dir" -name 7za.exe -recurse | select -first 1 #A select is because there is tools\7za.exe & tools\x64\7za.exe
|
||||
@@ -54,7 +55,7 @@ function Get-UmbracoBuildEnv
|
||||
if (-not (test-path $vswhere))
|
||||
{
|
||||
Write-Host "Download VsWhere..."
|
||||
&$nuget install vswhere -OutputDirectory $path -Verbosity quiet
|
||||
&$nuget install vswhere -configFile "$src\NuGet.config" -OutputDirectory $path -Verbosity quiet
|
||||
$dir = ls "$path\vswhere.*" | sort -property Name -descending | select -first 1
|
||||
$file = ls -path "$dir" -name vswhere.exe -recurse
|
||||
mv "$dir\$file" $vswhere
|
||||
@@ -70,7 +71,7 @@ function Get-UmbracoBuildEnv
|
||||
if (-not (test-path $semver))
|
||||
{
|
||||
Write-Host "Download Semver..."
|
||||
&$nuget install semver -OutputDirectory $path -Verbosity quiet
|
||||
&$nuget install semver -configFile "$src\NuGet.config" -OutputDirectory $path -Verbosity quiet
|
||||
$dir = ls "$path\semver.*" | sort -property Name -descending | select -first 1
|
||||
$file = "$dir\lib\net452\Semver.dll"
|
||||
if (-not (test-path $file))
|
||||
|
||||
@@ -376,7 +376,7 @@ function Prepare-Packages
|
||||
Copy-Files "$src\Umbraco.Web.UI\umbraco\js" "*" "$tmp\WebApp\umbraco\js"
|
||||
Copy-Files "$src\Umbraco.Web.UI\umbraco\lib" "*" "$tmp\WebApp\umbraco\lib"
|
||||
Copy-Files "$src\Umbraco.Web.UI\umbraco\views" "*" "$tmp\WebApp\umbraco\views"
|
||||
Copy-Files "$src\Umbraco.Web.UI\umbraco\preview" "*" "$tmp\WebApp\umbraco\preview"
|
||||
|
||||
}
|
||||
|
||||
#
|
||||
@@ -445,7 +445,7 @@ function Restore-NuGet
|
||||
Write-Host ">> Restore NuGet"
|
||||
Write-Host "Logging to $tmp\nuget.restore.log"
|
||||
|
||||
&$uenv.NuGet restore "$src\Umbraco.sln" > "$tmp\nuget.restore.log"
|
||||
&$uenv.NuGet restore "$src\Umbraco.sln" -configfile "$src\NuGet.config" > "$tmp\nuget.restore.log"
|
||||
}
|
||||
|
||||
#
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
<!-- Markdown can not be updated due to: https://github.com/hey-red/markdownsharp/issues/71#issuecomment-233585487 -->
|
||||
<dependency id="Markdown" version="[1.14.7, 2.0.0)" />
|
||||
<dependency id="System.Threading.Tasks.Dataflow" version="[4.7.0, 5.0.0)" />
|
||||
<dependency id="System.ValueTuple" version="[4.4.0, 5.0.0)" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
----------------------------------------------------
|
||||
|
||||
*** IMPORTANT NOTICE FOR 7.7 UPGRADES ***
|
||||
*** IMPORTANT NOTICE FOR UPGRADES FROM VERSIONS BELOW 7.7.0 ***
|
||||
|
||||
Be sure to read the version specific upgrade information before proceeding:
|
||||
https://our.umbraco.org/documentation/Getting-Started/Setup/Upgrading/version-specific#version-7-7-0
|
||||
|
||||
+1
-1
@@ -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
@@ -11,5 +11,5 @@ using System.Resources;
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
[assembly: AssemblyFileVersion("7.8.1")]
|
||||
[assembly: AssemblyInformationalVersion("7.8.1")]
|
||||
[assembly: AssemblyFileVersion("7.10.0")]
|
||||
[assembly: AssemblyInformationalVersion("7.10.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.1");
|
||||
private static readonly Version Version = new Version("7.10.0");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current version of Umbraco.
|
||||
|
||||
@@ -122,6 +122,11 @@ namespace Umbraco.Core
|
||||
/// Alias for the Dropdown list, publishing keys datatype.
|
||||
/// </summary>
|
||||
public const string DropdownlistPublishingKeysAlias = "Umbraco.DropdownlistPublishingKeys";
|
||||
|
||||
/// <summary>
|
||||
/// Alias for the "new" Dropdown list, that replaces the old four deprecated ones and works as other list based property editors
|
||||
/// </summary>
|
||||
public const string DropDownListFlexibleAlias = "Umbraco.DropDown.Flexible";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for the Folder browser datatype.
|
||||
@@ -452,4 +457,4 @@ namespace Umbraco.Core
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,6 +459,22 @@ namespace Umbraco.Core
|
||||
var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace);
|
||||
var connectionstrings = xml.Root.DescendantsAndSelf("connectionStrings").Single();
|
||||
|
||||
// honour configSource, if its set, change the xml file we are saving the configuration
|
||||
// to the one set in the configSource attribute
|
||||
if (connectionstrings.Attribute("configSource") != null)
|
||||
{
|
||||
var source = connectionstrings.Attribute("configSource").Value;
|
||||
var configFile = IOHelper.MapPath(string.Format("{0}/{1}", SystemDirectories.Root, source));
|
||||
LogHelper.Info<DatabaseContext>("storing ConnectionString in {0}", () => configFile);
|
||||
if (System.IO.File.Exists(configFile))
|
||||
{
|
||||
xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace);
|
||||
fileName = configFile;
|
||||
}
|
||||
|
||||
connectionstrings = xml.Root.DescendantsAndSelf("connectionStrings").Single();
|
||||
}
|
||||
|
||||
// Update connectionString if it exists, or else create a new appSetting for the given key and value
|
||||
var setting = connectionstrings.Descendants("add").FirstOrDefault(s => s.Attribute("name").Value == Constants.System.UmbracoConnectionName);
|
||||
if (setting == null)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Umbraco.Core.Configuration;
|
||||
@@ -26,6 +27,7 @@ namespace Umbraco.Core.IO
|
||||
private ShadowWrapper _xsltFileSystem;
|
||||
private ShadowWrapper _masterPagesFileSystem;
|
||||
private ShadowWrapper _mvcViewsFileSystem;
|
||||
private ShadowWrapper _javaScriptLibraryFileSystem;
|
||||
|
||||
#region Singleton & Constructor
|
||||
|
||||
@@ -113,6 +115,7 @@ namespace Umbraco.Core.IO
|
||||
var xsltFileSystem = new PhysicalFileSystem(SystemDirectories.Xslt);
|
||||
var masterPagesFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages);
|
||||
var mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews);
|
||||
var javaScriptLibraryFileSystem = new PhysicalFileSystem(Path.Combine(SystemDirectories.Umbraco, "lib"));
|
||||
|
||||
_macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "Views/MacroPartials", ScopeProvider);
|
||||
_partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "Views/Partials", ScopeProvider);
|
||||
@@ -123,6 +126,7 @@ namespace Umbraco.Core.IO
|
||||
_xsltFileSystem = new ShadowWrapper(xsltFileSystem, "xslt", ScopeProvider);
|
||||
_masterPagesFileSystem = new ShadowWrapper(masterPagesFileSystem, "masterpages", ScopeProvider);
|
||||
_mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "Views", ScopeProvider);
|
||||
_javaScriptLibraryFileSystem = new ShadowWrapper(javaScriptLibraryFileSystem, "Lib", ScopeProvider);
|
||||
|
||||
// filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again
|
||||
MediaFileSystem = GetFileSystemProvider<MediaFileSystem>();
|
||||
@@ -143,6 +147,7 @@ namespace Umbraco.Core.IO
|
||||
public IFileSystem2 XsltFileSystem { get { return _xsltFileSystem; } }
|
||||
public IFileSystem2 MasterPagesFileSystem { get { return _mvcViewsFileSystem; } }
|
||||
public IFileSystem2 MvcViewsFileSystem { get { return _mvcViewsFileSystem; } }
|
||||
internal IFileSystem2 JavaScriptLibraryFileSystem { get { return _javaScriptLibraryFileSystem; } }
|
||||
public MediaFileSystem MediaFileSystem { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -203,7 +203,6 @@ namespace Umbraco.Core.IO
|
||||
{
|
||||
get
|
||||
{
|
||||
//by default the packages folder should exist in the data folder
|
||||
return IOHelper.ReturnPath("umbracoPreviewPath", Data + IOHelper.DirSepChar + "preview");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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,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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,8 +140,7 @@ namespace Umbraco.Core
|
||||
if (underlying != null)
|
||||
{
|
||||
// Special case for empty strings for bools/dates which should return null if an empty string.
|
||||
var inputString = input as string;
|
||||
if (inputString != null)
|
||||
if (input is string inputString)
|
||||
{
|
||||
//TODO: Why the check against only bool/date when a string is null/empty? In what scenario can we convert to another type when the string is null or empty other than just being null?
|
||||
if (string.IsNullOrEmpty(inputString) && (underlying == typeof(DateTime) || underlying == typeof(bool)))
|
||||
@@ -168,8 +167,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
// target is not a generic type
|
||||
|
||||
var inputString = input as string;
|
||||
if (inputString != null)
|
||||
if (input is string inputString)
|
||||
{
|
||||
// Try convert from string, returns an Attempt if the string could be
|
||||
// processed (either succeeded or failed), else null if we need to try
|
||||
@@ -218,8 +216,7 @@ namespace Umbraco.Core
|
||||
}
|
||||
|
||||
// Re-check convertables since we altered the input through recursion
|
||||
var convertible2 = input as IConvertible;
|
||||
if (convertible2 != null)
|
||||
if (input is IConvertible convertible2)
|
||||
{
|
||||
return Attempt.Succeed(Convert.ChangeType(convertible2, target));
|
||||
}
|
||||
@@ -277,8 +274,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
if (target == typeof(int))
|
||||
{
|
||||
int value;
|
||||
if (int.TryParse(input, out value))
|
||||
if (int.TryParse(input, out var value))
|
||||
{
|
||||
return Attempt<object>.Succeed(value);
|
||||
}
|
||||
@@ -286,30 +282,26 @@ namespace Umbraco.Core
|
||||
// Because decimal 100.01m will happily convert to integer 100, it
|
||||
// makes sense that string "100.01" *also* converts to integer 100.
|
||||
var input2 = NormalizeNumberDecimalSeparator(input);
|
||||
decimal value2;
|
||||
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out value2), Convert.ToInt32(value2));
|
||||
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out var value2), Convert.ToInt32(value2));
|
||||
}
|
||||
|
||||
if (target == typeof(long))
|
||||
{
|
||||
long value;
|
||||
if (long.TryParse(input, out value))
|
||||
if (long.TryParse(input, out var value))
|
||||
{
|
||||
return Attempt<object>.Succeed(value);
|
||||
}
|
||||
|
||||
// Same as int
|
||||
var input2 = NormalizeNumberDecimalSeparator(input);
|
||||
decimal value2;
|
||||
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out value2), Convert.ToInt64(value2));
|
||||
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out var value2), Convert.ToInt64(value2));
|
||||
}
|
||||
|
||||
// TODO: Should we do the decimal trick for short, byte, unsigned?
|
||||
|
||||
if (target == typeof(bool))
|
||||
{
|
||||
bool value;
|
||||
if (bool.TryParse(input, out value))
|
||||
if (bool.TryParse(input, out var value))
|
||||
{
|
||||
return Attempt<object>.Succeed(value);
|
||||
}
|
||||
@@ -322,53 +314,42 @@ namespace Umbraco.Core
|
||||
switch (Type.GetTypeCode(target))
|
||||
{
|
||||
case TypeCode.Int16:
|
||||
short value;
|
||||
return Attempt<object>.SucceedIf(short.TryParse(input, out value), value);
|
||||
return Attempt<object>.SucceedIf(short.TryParse(input, out var value), value);
|
||||
|
||||
case TypeCode.Double:
|
||||
var input2 = NormalizeNumberDecimalSeparator(input);
|
||||
double valueD;
|
||||
return Attempt<object>.SucceedIf(double.TryParse(input2, out valueD), valueD);
|
||||
return Attempt<object>.SucceedIf(double.TryParse(input2, out var valueD), valueD);
|
||||
|
||||
case TypeCode.Single:
|
||||
var input3 = NormalizeNumberDecimalSeparator(input);
|
||||
float valueF;
|
||||
return Attempt<object>.SucceedIf(float.TryParse(input3, out valueF), valueF);
|
||||
return Attempt<object>.SucceedIf(float.TryParse(input3, out var valueF), valueF);
|
||||
|
||||
case TypeCode.Char:
|
||||
char valueC;
|
||||
return Attempt<object>.SucceedIf(char.TryParse(input, out valueC), valueC);
|
||||
return Attempt<object>.SucceedIf(char.TryParse(input, out var valueC), valueC);
|
||||
|
||||
case TypeCode.Byte:
|
||||
byte valueB;
|
||||
return Attempt<object>.SucceedIf(byte.TryParse(input, out valueB), valueB);
|
||||
return Attempt<object>.SucceedIf(byte.TryParse(input, out var valueB), valueB);
|
||||
|
||||
case TypeCode.SByte:
|
||||
sbyte valueSb;
|
||||
return Attempt<object>.SucceedIf(sbyte.TryParse(input, out valueSb), valueSb);
|
||||
return Attempt<object>.SucceedIf(sbyte.TryParse(input, out var valueSb), valueSb);
|
||||
|
||||
case TypeCode.UInt32:
|
||||
uint valueU;
|
||||
return Attempt<object>.SucceedIf(uint.TryParse(input, out valueU), valueU);
|
||||
return Attempt<object>.SucceedIf(uint.TryParse(input, out var valueU), valueU);
|
||||
|
||||
case TypeCode.UInt16:
|
||||
ushort valueUs;
|
||||
return Attempt<object>.SucceedIf(ushort.TryParse(input, out valueUs), valueUs);
|
||||
return Attempt<object>.SucceedIf(ushort.TryParse(input, out var valueUs), valueUs);
|
||||
|
||||
case TypeCode.UInt64:
|
||||
ulong valueUl;
|
||||
return Attempt<object>.SucceedIf(ulong.TryParse(input, out valueUl), valueUl);
|
||||
return Attempt<object>.SucceedIf(ulong.TryParse(input, out var valueUl), valueUl);
|
||||
}
|
||||
}
|
||||
else if (target == typeof(Guid))
|
||||
{
|
||||
Guid value;
|
||||
return Attempt<object>.SucceedIf(Guid.TryParse(input, out value), value);
|
||||
return Attempt<object>.SucceedIf(Guid.TryParse(input, out var value), value);
|
||||
}
|
||||
else if (target == typeof(DateTime))
|
||||
{
|
||||
DateTime value;
|
||||
if (DateTime.TryParse(input, out value))
|
||||
if (DateTime.TryParse(input, out var value))
|
||||
{
|
||||
switch (value.Kind)
|
||||
{
|
||||
@@ -388,24 +369,20 @@ namespace Umbraco.Core
|
||||
}
|
||||
else if (target == typeof(DateTimeOffset))
|
||||
{
|
||||
DateTimeOffset value;
|
||||
return Attempt<object>.SucceedIf(DateTimeOffset.TryParse(input, out value), value);
|
||||
return Attempt<object>.SucceedIf(DateTimeOffset.TryParse(input, out var value), value);
|
||||
}
|
||||
else if (target == typeof(TimeSpan))
|
||||
{
|
||||
TimeSpan value;
|
||||
return Attempt<object>.SucceedIf(TimeSpan.TryParse(input, out value), value);
|
||||
return Attempt<object>.SucceedIf(TimeSpan.TryParse(input, out var value), value);
|
||||
}
|
||||
else if (target == typeof(decimal))
|
||||
{
|
||||
var input2 = NormalizeNumberDecimalSeparator(input);
|
||||
decimal value;
|
||||
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out value), value);
|
||||
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out var value), value);
|
||||
}
|
||||
else if (input != null && target == typeof(Version))
|
||||
{
|
||||
Version value;
|
||||
return Attempt<object>.SucceedIf(Version.TryParse(input, out value), value);
|
||||
return Attempt<object>.SucceedIf(Version.TryParse(input, out var value), value);
|
||||
}
|
||||
|
||||
// E_NOTIMPL IPAddress, BigInteger
|
||||
@@ -690,8 +667,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
var key = new CompositeTypeTypeKey(source, target);
|
||||
|
||||
TypeConverter typeConverter;
|
||||
if (InputTypeConverterCache.TryGetValue(key, out typeConverter))
|
||||
if (InputTypeConverterCache.TryGetValue(key, out TypeConverter typeConverter))
|
||||
{
|
||||
return typeConverter;
|
||||
}
|
||||
@@ -711,8 +687,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
var key = new CompositeTypeTypeKey(source, target);
|
||||
|
||||
TypeConverter typeConverter;
|
||||
if (DestinationTypeConverterCache.TryGetValue(key, out typeConverter))
|
||||
if (DestinationTypeConverterCache.TryGetValue(key, out TypeConverter typeConverter))
|
||||
{
|
||||
return typeConverter;
|
||||
}
|
||||
@@ -730,8 +705,7 @@ namespace Umbraco.Core
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Type GetCachedGenericNullableType(Type type)
|
||||
{
|
||||
Type underlyingType;
|
||||
if (NullableGenericCache.TryGetValue(type, out underlyingType))
|
||||
if (NullableGenericCache.TryGetValue(type, out Type underlyingType))
|
||||
{
|
||||
return underlyingType;
|
||||
}
|
||||
@@ -750,8 +724,7 @@ namespace Umbraco.Core
|
||||
private static bool GetCachedCanAssign(object input, Type source, Type target)
|
||||
{
|
||||
var key = new CompositeTypeTypeKey(source, target);
|
||||
bool canConvert;
|
||||
if (AssignableTypeCache.TryGetValue(key, out canConvert))
|
||||
if (AssignableTypeCache.TryGetValue(key, out bool canConvert))
|
||||
{
|
||||
return canConvert;
|
||||
}
|
||||
@@ -770,8 +743,7 @@ namespace Umbraco.Core
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool GetCachedCanConvertToBoolean(Type type)
|
||||
{
|
||||
bool result;
|
||||
if (BoolConvertCache.TryGetValue(type, out result))
|
||||
if (BoolConvertCache.TryGetValue(type, out bool result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+35
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
-7
@@ -6,6 +6,7 @@ using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero
|
||||
@@ -343,15 +344,24 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe
|
||||
|
||||
if (tables.InvariantContains("umbracoUserType") && tables.InvariantContains("umbracoUser"))
|
||||
{
|
||||
if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser") && x.Item3.InvariantEquals("FK_umbracoUser_umbracoUserType_id")))
|
||||
if (Context.CurrentDatabaseProvider == DatabaseProviders.MySql)
|
||||
{
|
||||
Delete.ForeignKey("FK_umbracoUser_umbracoUserType_id").OnTable("umbracoUser");
|
||||
//In MySql, this will drop the FK according to it's special naming rules
|
||||
Delete.ForeignKey().FromTable("umbracoUser").ForeignColumn("userType").ToTable("umbracoUserType").PrimaryColumn("id");
|
||||
}
|
||||
//This is the super old constraint name of the FK for user type so check this one too
|
||||
if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser") && x.Item3.InvariantEquals("FK_user_userType")))
|
||||
else
|
||||
{
|
||||
Delete.ForeignKey("FK_user_userType").OnTable("umbracoUser");
|
||||
}
|
||||
//Delete the FK if it exists before dropping the column
|
||||
if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser") && x.Item3.InvariantEquals("FK_umbracoUser_umbracoUserType_id")))
|
||||
{
|
||||
Delete.ForeignKey("FK_umbracoUser_umbracoUserType_id").OnTable("umbracoUser");
|
||||
}
|
||||
//This is the super old constraint name of the FK for user type so check this one too
|
||||
if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser") && x.Item3.InvariantEquals("FK_user_userType")))
|
||||
{
|
||||
Delete.ForeignKey("FK_user_userType").OnTable("umbracoUser");
|
||||
}
|
||||
}
|
||||
|
||||
Delete.Column("userType").FromTable("umbracoUser");
|
||||
Delete.Table("umbracoUserType");
|
||||
@@ -361,4 +371,4 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe
|
||||
public override void Down()
|
||||
{ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
using System.IO;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using File = System.IO.File;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenTenZero
|
||||
{
|
||||
/// <summary>
|
||||
/// Renames the preview folder containing static html files to ensure it does not interfere with the MVC route
|
||||
/// that is now supposed to render these views dynamically. We don't want to delete as people may have made
|
||||
/// customizations to these files that would need to be migrated to the new .cshtml view files.
|
||||
/// </summary>
|
||||
[Migration("7.10.0", 1, Constants.System.UmbracoMigrationName)]
|
||||
public class RenamePreviewFolder : MigrationBase
|
||||
{
|
||||
public RenamePreviewFolder(ISqlSyntaxProvider sqlSyntax, ILogger logger)
|
||||
: base(sqlSyntax, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
var previewFolderPath = IOHelper.MapPath(SystemDirectories.Umbraco + "/preview");
|
||||
if (Directory.Exists(previewFolderPath))
|
||||
{
|
||||
var newPath = previewFolderPath.Replace("preview", "preview.old");
|
||||
if (Directory.Exists(newPath) == false)
|
||||
{
|
||||
Directory.Move(previewFolderPath, newPath);
|
||||
var readmeText =
|
||||
$"Static html files used for preview and canvas editing functionality no longer live in this directory.\r\n" +
|
||||
$"Instead they have been recreated as MVC views and can now be found in '~/Umbraco/Views/Preview'.\r\n" +
|
||||
$"See issue: http://issues.umbraco.org/issue/U4-11090";
|
||||
File.WriteAllText(Path.Combine(newPath, "readme.txt"), readmeText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using System;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Core.PropertyEditors.ValueConverters
|
||||
{
|
||||
@@ -14,19 +15,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
|
||||
|
||||
public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview)
|
||||
{
|
||||
if (source == null) return 0;
|
||||
|
||||
// in XML an integer is a string
|
||||
var sourceString = source as string;
|
||||
if (sourceString != null)
|
||||
{
|
||||
int i;
|
||||
return (int.TryParse(sourceString, out i)) ? i : 0;
|
||||
}
|
||||
|
||||
// in the database an integer is an integer
|
||||
// default value is zero
|
||||
return (source is int) ? source : 0;
|
||||
return source.TryConvertTo<int>().Result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -37,15 +37,15 @@ namespace Umbraco.Core.Security
|
||||
var username = identity.GetUserName();
|
||||
var session = identity.FindFirstValue(Constants.Security.SessionIdClaimType);
|
||||
var securityStamp = identity.FindFirstValue(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType);
|
||||
var startContentId = identity.FindFirstValue(Constants.Security.StartContentNodeIdClaimType);
|
||||
var startContentId = identity.FindFirstValue(Constants.Security.StartContentNodeIdClaimType);
|
||||
var startMediaId = identity.FindFirstValue(Constants.Security.StartMediaNodeIdClaimType);
|
||||
|
||||
var culture = identity.FindFirstValue(ClaimTypes.Locality);
|
||||
var id = identity.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
var id = identity.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
var realName = identity.FindFirstValue(ClaimTypes.GivenName);
|
||||
|
||||
if (username == null || startContentId == null || startMediaId == null
|
||||
|| culture == null || id == null
|
||||
if (username == null || startContentId == null || startMediaId == null
|
||||
|| culture == null || id == null
|
||||
|| realName == null || session == null)
|
||||
throw new InvalidOperationException("Cannot create a " + typeof(UmbracoBackOfficeIdentity) + " from " + typeof(ClaimsIdentity) + " since there are missing required claims");
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace Umbraco.Core.Security
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot create a " + typeof(UmbracoBackOfficeIdentity) + " from " + typeof(ClaimsIdentity) + " since the data is not formatted correctly - either content or media start Ids could not be parsed as JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
var roles = identity.FindAll(x => x.Type == DefaultRoleClaimType).Select(role => role.Value).ToList();
|
||||
var allowedApps = identity.FindAll(x => x.Type == Constants.Security.AllowedApplicationsClaimType).Select(app => app.Value).ToList();
|
||||
@@ -165,7 +165,7 @@ namespace Umbraco.Core.Security
|
||||
{
|
||||
foreach (var claim in claimsIdentity.Claims)
|
||||
{
|
||||
//In one special case we will replace a claim if it exists already and that is the
|
||||
//In one special case we will replace a claim if it exists already and that is the
|
||||
// Forms auth claim for name which automatically gets added
|
||||
TryRemoveClaim(FindFirst(x => x.Type == claim.Type && x.Issuer == "Forms"));
|
||||
|
||||
@@ -187,15 +187,15 @@ namespace Umbraco.Core.Security
|
||||
{
|
||||
ClaimTypes.NameIdentifier, //id
|
||||
ClaimTypes.Name, //username
|
||||
ClaimTypes.GivenName,
|
||||
ClaimTypes.GivenName,
|
||||
Constants.Security.StartContentNodeIdClaimType,
|
||||
Constants.Security.StartMediaNodeIdClaimType,
|
||||
ClaimTypes.Locality,
|
||||
Constants.Security.StartMediaNodeIdClaimType,
|
||||
ClaimTypes.Locality,
|
||||
Constants.Security.SessionIdClaimType,
|
||||
Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds claims based on the UserData data
|
||||
@@ -222,23 +222,19 @@ namespace Umbraco.Core.Security
|
||||
AddClaim(new Claim(ClaimTypes.Locality, Culture, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
|
||||
if (HasClaim(x => x.Type == Constants.Security.SessionIdClaimType) == false && SessionId.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
AddClaim(new Claim(Constants.Security.SessionIdClaimType, SessionId, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
|
||||
//The security stamp claim is also required... this is because this claim type is hard coded
|
||||
// by the SecurityStampValidator, see: https://katanaproject.codeplex.com/workitem/444
|
||||
if (HasClaim(x => x.Type == Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType) == false)
|
||||
{
|
||||
AddClaim(new Claim(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType, SecurityStamp, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
}
|
||||
}
|
||||
//The security stamp claim is also required... this is because this claim type is hard coded
|
||||
// by the SecurityStampValidator, see: https://katanaproject.codeplex.com/workitem/444
|
||||
if (HasClaim(x => x.Type == Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType) == false)
|
||||
AddClaim(new Claim(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType, SecurityStamp, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
|
||||
//Add each app as a separate claim
|
||||
if (HasClaim(x => x.Type == Constants.Security.AllowedApplicationsClaimType) == false)
|
||||
{
|
||||
foreach (var application in AllowedApplications)
|
||||
{
|
||||
AddClaim(new Claim(Constants.Security.AllowedApplicationsClaimType, application, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
AddClaim(new Claim(Constants.Security.AllowedApplicationsClaimType, application, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,8 +249,8 @@ namespace Umbraco.Core.Security
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected internal UserData UserData { get; private set; }
|
||||
@@ -332,4 +328,4 @@ namespace Umbraco.Core.Security
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -22,13 +23,139 @@ namespace Umbraco.Core.Services
|
||||
// note - no need for uow, scope would be enough, but a pain to wire
|
||||
// note - for pure read-only we might want to *not* enforce a transaction?
|
||||
|
||||
// notes
|
||||
//
|
||||
// - this class assumes that the id/guid map is unique; that is, if an id and a guid map
|
||||
// to each other, then the id will never map to another guid, and the guid will never map
|
||||
// to another id
|
||||
//
|
||||
// - LeeK's solution in 7.7 was to look for the id/guid in the content cache "on demand" via
|
||||
// XPath, which is probably fast enough but cannot deal with media ids + it maintains a
|
||||
// separate, duplicate cache
|
||||
// see https://github.com/umbraco/Umbraco-CMS/pull/2398
|
||||
//
|
||||
// - Andy's solution in a package was to prefetch all by sql; it cannot prefecth reserved ids
|
||||
// as we don't know the corresponding object type, but that's not a big issue - but then we
|
||||
// have a full database query on startup
|
||||
// see https://github.com/AndyButland/UmbracoUdiToIdCache
|
||||
//
|
||||
// - the original IdkMap implementation that was used by services, did a database lookup on
|
||||
// each cache miss, which is fine enough for services, but would be really slow at content
|
||||
// cache level
|
||||
//
|
||||
// - cache is cleared by MediaCacheRefresher, UnpublishedPageCacheRefresher, and other
|
||||
// refreshers - because id/guid map is unique, we only clear to avoid leaking memory, 'cos
|
||||
// we don't risk caching obsolete values - and only when actually deleting
|
||||
//
|
||||
// so...
|
||||
//
|
||||
// - there's a single caching point, and it's idkMap
|
||||
// - there are no "helper methods" - the published content cache itself knows about Guids
|
||||
// - when the published content cache is instanciated, it populates the idkMap with what it knows
|
||||
// and it registers a way for the idkMap to look for id/keys in the published content cache
|
||||
// - we do NOT prefetch anything from database
|
||||
// - when a request comes in:
|
||||
// the published content cache uses the idkMap to map id/key
|
||||
// if the idkMap already knows about the map, it returns the value
|
||||
// else it tries the published cache via XPath
|
||||
// else it hits the database
|
||||
|
||||
|
||||
private readonly ConcurrentDictionary<UmbracoObjectTypes, (Func<int, Guid> id2key, Func<Guid, int> key2id)> _dictionary
|
||||
= new ConcurrentDictionary<UmbracoObjectTypes, (Func<int, Guid> id2key, Func<Guid, int> key2id)>();
|
||||
|
||||
internal void SetMapper(UmbracoObjectTypes umbracoObjectType, Func<int, Guid> id2key, Func<Guid, int> key2id)
|
||||
{
|
||||
_dictionary[umbracoObjectType] = (id2key, key2id);
|
||||
}
|
||||
|
||||
internal void Populate(IEnumerable<(int id, Guid key)> pairs, UmbracoObjectTypes umbracoObjectType)
|
||||
{
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
_id2Key.Add(pair.id, new TypedId<Guid>(pair.key, umbracoObjectType));
|
||||
_key2Id.Add(pair.key, new TypedId<int>(pair.id, umbracoObjectType));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_locker.IsWriteLockHeld)
|
||||
_locker.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
#if POPULATE_FROM_DATABASE
|
||||
private void PopulateLocked()
|
||||
{
|
||||
// don't if not empty
|
||||
if (_key2Id.Count > 0) return;
|
||||
|
||||
using (var uow = _uowProvider.GetUnitOfWork())
|
||||
{
|
||||
// populate content and media items
|
||||
var types = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media };
|
||||
var values = uow.Database.Query<TypedIdDto>("SELECT id, uniqueId, nodeObjectType FROM umbracoNode WHERE nodeObjectType IN @types", new { types });
|
||||
foreach (var value in values)
|
||||
{
|
||||
var umbracoObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(value.NodeObjectType);
|
||||
_id2Key.Add(value.Id, new TypedId<Guid>(value.UniqueId, umbracoObjectType));
|
||||
_key2Id.Add(value.UniqueId, new TypedId<int>(value.Id, umbracoObjectType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Attempt<int> PopulateAndGetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType)
|
||||
{
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
|
||||
PopulateLocked();
|
||||
|
||||
return _key2Id.TryGetValue(key, out var id) && id.UmbracoObjectType == umbracoObjectType
|
||||
? Attempt.Succeed(id.Id)
|
||||
: Attempt<int>.Fail();
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_locker.IsReadLockHeld)
|
||||
_locker.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
private Attempt<Guid> PopulateAndGetKeyForId(int id, UmbracoObjectTypes umbracoObjectType)
|
||||
{
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
|
||||
PopulateLocked();
|
||||
|
||||
return _id2Key.TryGetValue(id, out var key) && key.UmbracoObjectType == umbracoObjectType
|
||||
? Attempt.Succeed(key.Id)
|
||||
: Attempt<Guid>.Fail();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_locker.IsReadLockHeld)
|
||||
_locker.ExitReadLock();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public Attempt<int> GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType)
|
||||
{
|
||||
TypedId<int> id;
|
||||
bool empty;
|
||||
|
||||
try
|
||||
{
|
||||
_locker.EnterReadLock();
|
||||
if (_key2Id.TryGetValue(key, out id) && id.UmbracoObjectType == umbracoObjectType) return Attempt.Succeed(id.Id);
|
||||
if (_key2Id.TryGetValue(key, out var id) && id.UmbracoObjectType == umbracoObjectType) return Attempt.Succeed(id.Id);
|
||||
empty = _key2Id.Count == 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -36,20 +163,37 @@ namespace Umbraco.Core.Services
|
||||
_locker.ExitReadLock();
|
||||
}
|
||||
|
||||
int? val;
|
||||
using (var uow = _uowProvider.GetUnitOfWork())
|
||||
#if POPULATE_FROM_DATABASE
|
||||
// if cache is empty and looking for a document or a media,
|
||||
// populate the cache at once and return what we found
|
||||
if (empty && (umbracoObjectType == UmbracoObjectTypes.Document || umbracoObjectType == UmbracoObjectTypes.Media))
|
||||
return PopulateAndGetIdForKey(key, umbracoObjectType);
|
||||
#endif
|
||||
|
||||
// optimize for read speed: reading database outside a lock means that we could read
|
||||
// multiple times, but we don't lock the cache while accessing the database = better
|
||||
|
||||
int? val = null;
|
||||
|
||||
if (_dictionary.TryGetValue(umbracoObjectType, out var mappers))
|
||||
if ((val = mappers.key2id(key)) == default(int)) val = null;
|
||||
|
||||
if (val == null)
|
||||
{
|
||||
//if it's unknown don't include the nodeObjectType in the query
|
||||
if (umbracoObjectType == UmbracoObjectTypes.Unknown)
|
||||
using (var uow = _uowProvider.GetUnitOfWork())
|
||||
{
|
||||
val = uow.Database.ExecuteScalar<int?>("SELECT id FROM umbracoNode WHERE uniqueId=@id", new { id = key});
|
||||
//if it's unknown don't include the nodeObjectType in the query
|
||||
if (umbracoObjectType == UmbracoObjectTypes.Unknown)
|
||||
{
|
||||
val = uow.Database.ExecuteScalar<int?>("SELECT id FROM umbracoNode WHERE uniqueId=@id", new { id = key});
|
||||
}
|
||||
else
|
||||
{
|
||||
val = uow.Database.ExecuteScalar<int?>("SELECT id FROM umbracoNode WHERE uniqueId=@id AND (nodeObjectType=@type OR nodeObjectType=@reservation)",
|
||||
new { id = key, type = GetNodeObjectTypeGuid(umbracoObjectType), reservation = Constants.ObjectTypes.IdReservationGuid });
|
||||
}
|
||||
uow.Commit();
|
||||
}
|
||||
else
|
||||
{
|
||||
val = uow.Database.ExecuteScalar<int?>("SELECT id FROM umbracoNode WHERE uniqueId=@id AND (nodeObjectType=@type OR nodeObjectType=@reservation)",
|
||||
new { id = key, type = GetNodeObjectTypeGuid(umbracoObjectType), reservation = Constants.ObjectTypes.IdReservationGuid });
|
||||
}
|
||||
uow.Commit();
|
||||
}
|
||||
|
||||
if (val == null) return Attempt<int>.Fail();
|
||||
@@ -83,13 +227,23 @@ namespace Umbraco.Core.Services
|
||||
return GetIdForKey(guidUdi.Guid, umbracoType);
|
||||
}
|
||||
|
||||
public Attempt<Udi> GetUdiForId(int id, UmbracoObjectTypes umbracoObjectType)
|
||||
{
|
||||
var keyAttempt = GetKeyForId(id, umbracoObjectType);
|
||||
return keyAttempt
|
||||
? Attempt.Succeed<Udi>(new GuidUdi(Constants.UdiEntityType.FromUmbracoObjectType(umbracoObjectType), keyAttempt.Result))
|
||||
: Attempt<Udi>.Fail();
|
||||
}
|
||||
|
||||
public Attempt<Guid> GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType)
|
||||
{
|
||||
TypedId<Guid> key;
|
||||
bool empty;
|
||||
|
||||
try
|
||||
{
|
||||
_locker.EnterReadLock();
|
||||
if (_id2Key.TryGetValue(id, out key) && key.UmbracoObjectType == umbracoObjectType) return Attempt.Succeed(key.Id);
|
||||
if (_id2Key.TryGetValue(id, out var key) && key.UmbracoObjectType == umbracoObjectType) return Attempt.Succeed(key.Id);
|
||||
empty = _id2Key.Count == 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -97,20 +251,37 @@ namespace Umbraco.Core.Services
|
||||
_locker.ExitReadLock();
|
||||
}
|
||||
|
||||
Guid? val;
|
||||
using (var uow = _uowProvider.GetUnitOfWork())
|
||||
#if POPULATE_FROM_DATABASE
|
||||
// if cache is empty and looking for a document or a media,
|
||||
// populate the cache at once and return what we found
|
||||
if (empty && (umbracoObjectType == UmbracoObjectTypes.Document || umbracoObjectType == UmbracoObjectTypes.Media))
|
||||
return PopulateAndGetKeyForId(id, umbracoObjectType);
|
||||
#endif
|
||||
|
||||
// optimize for read speed: reading database outside a lock means that we could read
|
||||
// multiple times, but we don't lock the cache while accessing the database = better
|
||||
|
||||
Guid? val = null;
|
||||
|
||||
if (_dictionary.TryGetValue(umbracoObjectType, out var mappers))
|
||||
if ((val = mappers.id2key(id)) == default(Guid)) val = null;
|
||||
|
||||
if (val == null)
|
||||
{
|
||||
//if it's unknown don't include the nodeObjectType in the query
|
||||
if (umbracoObjectType == UmbracoObjectTypes.Unknown)
|
||||
using (var uow = _uowProvider.GetUnitOfWork())
|
||||
{
|
||||
val = uow.Database.ExecuteScalar<Guid?>("SELECT uniqueId FROM umbracoNode WHERE id=@id", new { id });
|
||||
//if it's unknown don't include the nodeObjectType in the query
|
||||
if (umbracoObjectType == UmbracoObjectTypes.Unknown)
|
||||
{
|
||||
val = uow.Database.ExecuteScalar<Guid?>("SELECT uniqueId FROM umbracoNode WHERE id=@id", new { id });
|
||||
}
|
||||
else
|
||||
{
|
||||
val = uow.Database.ExecuteScalar<Guid?>("SELECT uniqueId FROM umbracoNode WHERE id=@id AND (nodeObjectType=@type OR nodeObjectType=@reservation)",
|
||||
new { id, type = GetNodeObjectTypeGuid(umbracoObjectType), reservation = Constants.ObjectTypes.IdReservationGuid });
|
||||
}
|
||||
uow.Commit();
|
||||
}
|
||||
else
|
||||
{
|
||||
val = uow.Database.ExecuteScalar<Guid?>("SELECT uniqueId FROM umbracoNode WHERE id=@id AND (nodeObjectType=@type OR nodeObjectType=@reservation)",
|
||||
new { id, type = GetNodeObjectTypeGuid(umbracoObjectType), reservation = Constants.ObjectTypes.IdReservationGuid });
|
||||
}
|
||||
uow.Commit();
|
||||
}
|
||||
|
||||
if (val == null) return Attempt<Guid>.Fail();
|
||||
@@ -142,6 +313,8 @@ namespace Umbraco.Core.Services
|
||||
return guid;
|
||||
}
|
||||
|
||||
// invoked on UnpublishedPageCacheRefresher.RefreshAll
|
||||
// anything else will use the id-specific overloads
|
||||
public void ClearCache()
|
||||
{
|
||||
try
|
||||
@@ -162,8 +335,7 @@ namespace Umbraco.Core.Services
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
TypedId<Guid> key;
|
||||
if (_id2Key.TryGetValue(id, out key) == false) return;
|
||||
if (_id2Key.TryGetValue(id, out var key) == false) return;
|
||||
_id2Key.Remove(id);
|
||||
_key2Id.Remove(key.Id);
|
||||
}
|
||||
@@ -179,8 +351,7 @@ namespace Umbraco.Core.Services
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
TypedId<int> id;
|
||||
if (_key2Id.TryGetValue(key, out id) == false) return;
|
||||
if (_key2Id.TryGetValue(key, out var id) == false) return;
|
||||
_id2Key.Remove(id.Id);
|
||||
_key2Id.Remove(key);
|
||||
}
|
||||
@@ -191,26 +362,28 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable ClassNeverInstantiated.Local
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Local
|
||||
private class TypedIdDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public Guid UniqueId { get; set; }
|
||||
public Guid NodeObjectType { get; set; }
|
||||
}
|
||||
// ReSharper restore ClassNeverInstantiated.Local
|
||||
// ReSharper restore UnusedAutoPropertyAccessor.Local
|
||||
|
||||
private struct TypedId<T>
|
||||
{
|
||||
private readonly T _id;
|
||||
private readonly UmbracoObjectTypes _umbracoObjectType;
|
||||
|
||||
public T Id
|
||||
{
|
||||
get { return _id; }
|
||||
}
|
||||
|
||||
public UmbracoObjectTypes UmbracoObjectType
|
||||
{
|
||||
get { return _umbracoObjectType; }
|
||||
}
|
||||
|
||||
public TypedId(T id, UmbracoObjectTypes umbracoObjectType)
|
||||
{
|
||||
_umbracoObjectType = umbracoObjectType;
|
||||
_id = id;
|
||||
UmbracoObjectType = umbracoObjectType;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public UmbracoObjectTypes UmbracoObjectType { get; }
|
||||
|
||||
public T Id { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,20 +207,17 @@ namespace Umbraco.Core.Services
|
||||
{
|
||||
return repository.GetByUsername(username, includeSecurityData: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (DbException ex)
|
||||
{
|
||||
if (ex is SqlException || ex is SqlCeException)
|
||||
//we need to handle this one specific case which is when we are upgrading to 7.7 since the user group
|
||||
//tables don't exist yet. This is the 'easiest' way to deal with this without having to create special
|
||||
//version checks in the BackOfficeSignInManager and calling into other special overloads that we'd need
|
||||
//like "GetUserById(int id, bool includeSecurityData)" which may cause confusion because the result of
|
||||
//that method would not be cached.
|
||||
if (ApplicationContext.Current.IsUpgrading)
|
||||
{
|
||||
//we need to handle this one specific case which is when we are upgrading to 7.7 since the user group
|
||||
//tables don't exist yet. This is the 'easiest' way to deal with this without having to create special
|
||||
//version checks in the BackOfficeSignInManager and calling into other special overloads that we'd need
|
||||
//like "GetUserById(int id, bool includeSecurityData)" which may cause confusion because the result of
|
||||
//that method would not be cached.
|
||||
if (ApplicationContext.Current.IsUpgrading)
|
||||
{
|
||||
//NOTE: this will not be cached
|
||||
return repository.GetByUsername(username, includeSecurityData: false);
|
||||
}
|
||||
//NOTE: this will not be cached
|
||||
return repository.GetByUsername(username, includeSecurityData: false);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
@@ -789,20 +786,17 @@ namespace Umbraco.Core.Services
|
||||
var result = repository.Get(id);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (DbException ex)
|
||||
{
|
||||
if (ex is SqlException || ex is SqlCeException)
|
||||
//we need to handle this one specific case which is when we are upgrading to 7.7 since the user group
|
||||
//tables don't exist yet. This is the 'easiest' way to deal with this without having to create special
|
||||
//version checks in the BackOfficeSignInManager and calling into other special overloads that we'd need
|
||||
//like "GetUserById(int id, bool includeSecurityData)" which may cause confusion because the result of
|
||||
//that method would not be cached.
|
||||
if (ApplicationContext.Current.IsUpgrading)
|
||||
{
|
||||
//we need to handle this one specific case which is when we are upgrading to 7.7 since the user group
|
||||
//tables don't exist yet. This is the 'easiest' way to deal with this without having to create special
|
||||
//version checks in the BackOfficeSignInManager and calling into other special overloads that we'd need
|
||||
//like "GetUserById(int id, bool includeSecurityData)" which may cause confusion because the result of
|
||||
//that method would not be cached.
|
||||
if (ApplicationContext.Current.IsUpgrading)
|
||||
{
|
||||
//NOTE: this will not be cached
|
||||
return repository.Get(id, includeSecurityData: false);
|
||||
}
|
||||
//NOTE: this will not be cached
|
||||
return repository.Get(id, includeSecurityData: false);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
@@ -838,13 +832,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 +855,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 +927,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 +936,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 +971,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 +1170,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 +1207,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 +1305,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>
|
||||
|
||||
@@ -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>
|
||||
@@ -113,6 +115,9 @@
|
||||
<Reference Include="System.Runtime.Caching" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Transactions" />
|
||||
<Reference Include="System.ValueTuple, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Web.ApplicationServices" />
|
||||
<Reference Include="System.Web.Extensions" />
|
||||
@@ -137,6 +142,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 +358,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 +371,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 +548,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 +559,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 +573,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" />
|
||||
@@ -581,6 +606,7 @@
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSixZero\NormalizeTemplateGuids.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSixZero\RemovePropertyDataIdIndex.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSixZero\RemoveUmbracoDeployTables.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenTenZero\RenamePreviewFolder.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeTwo\EnsureMigrationsTableIdentityIsCorrect.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenFourZero\EnsureContentTypeUniqueIdsAreConsistent.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\AddExternalLoginsTable.cs" />
|
||||
@@ -622,12 +648,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 +755,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 +782,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 +1677,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>
|
||||
@@ -3,6 +3,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
@@ -142,10 +143,18 @@ namespace Umbraco.Core
|
||||
/// <returns></returns>
|
||||
internal static bool IsClientSideRequest(this Uri url)
|
||||
{
|
||||
var ext = Path.GetExtension(url.LocalPath);
|
||||
if (ext.IsNullOrWhiteSpace()) return false;
|
||||
var toInclude = new[] { ".aspx", ".ashx", ".asmx", ".axd", ".svc" };
|
||||
return toInclude.Any(ext.InvariantEquals) == false;
|
||||
try
|
||||
{
|
||||
var ext = Path.GetExtension(url.LocalPath);
|
||||
if (ext.IsNullOrWhiteSpace()) return false;
|
||||
var toInclude = new[] {".aspx", ".ashx", ".asmx", ".axd", ".svc"};
|
||||
return toInclude.Any(ext.InvariantEquals) == false;
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
LogHelper.Error(typeof(UriExtensions), "Failed to determine if request was client side", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -311,4 +320,4 @@ namespace Umbraco.Core
|
||||
return new Uri(uri.GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,4 +18,5 @@
|
||||
<package id="Owin" version="1.0" targetFramework="net45" />
|
||||
<package id="semver" version="1.1.2" targetFramework="net45" />
|
||||
<package id="SqlServerCE" version="4.0.0.1" targetFramework="net45" />
|
||||
<package id="System.ValueTuple" version="4.4.0" targetFramework="net45" />
|
||||
</packages>
|
||||
@@ -139,8 +139,8 @@
|
||||
<Reference Include="System.Threading.Thread, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ValueTuple, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
|
||||
<Reference Include="System.ValueTuple, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
|
||||
@@ -62,6 +62,10 @@
|
||||
<assemblyIdentity name="System.Xml.XPath.XDocument" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -51,7 +51,7 @@
|
||||
<package id="System.Threading.Tasks.Extensions" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Threading.Tasks.Parallel" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Threading.Thread" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.ValueTuple" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.ValueTuple" version="4.4.0" targetFramework="net46" />
|
||||
<package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Xml.XmlDocument" version="4.3.0" targetFramework="net46" />
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -493,18 +494,18 @@ namespace Umbraco.Tests.Models.Mapping
|
||||
|
||||
//TODO: Now we need to assert all of the more complicated parts
|
||||
|
||||
Assert.AreEqual(contentType.PropertyGroups.Count(), result.Groups.Count());
|
||||
for (var i = 0; i < contentType.PropertyGroups.Count(); i++)
|
||||
Assert.AreEqual(contentType.PropertyGroups.Count, result.Groups.Count());
|
||||
for (var i = 0; i < contentType.PropertyGroups.Count; i++)
|
||||
{
|
||||
Assert.AreEqual(contentType.PropertyGroups.ElementAt(i).Id, result.Groups.ElementAt(i).Id);
|
||||
Assert.AreEqual(contentType.PropertyGroups.ElementAt(i).Name, result.Groups.ElementAt(i).Name);
|
||||
var propTypes = contentType.PropertyGroups.ElementAt(i).PropertyTypes;
|
||||
Assert.AreEqual(contentType.PropertyGroups[i].Id, result.Groups.ElementAt(i).Id);
|
||||
Assert.AreEqual(contentType.PropertyGroups[i].Name, result.Groups.ElementAt(i).Name);
|
||||
var propTypes = contentType.PropertyGroups[i].PropertyTypes;
|
||||
|
||||
Assert.AreEqual(propTypes.Count(), result.Groups.ElementAt(i).Properties.Count());
|
||||
for (var j = 0; j < propTypes.Count(); j++)
|
||||
Assert.AreEqual(propTypes.Count, result.Groups.ElementAt(i).Properties.Count());
|
||||
for (var j = 0; j < propTypes.Count; j++)
|
||||
{
|
||||
Assert.AreEqual(propTypes.ElementAt(j).Id, result.Groups.ElementAt(i).Properties.ElementAt(j).Id);
|
||||
Assert.AreEqual(propTypes.ElementAt(j).DataTypeDefinitionId, result.Groups.ElementAt(i).Properties.ElementAt(j).DataTypeId);
|
||||
Assert.AreEqual(propTypes[j].Id, result.Groups.ElementAt(i).Properties.ElementAt(j).Id);
|
||||
Assert.AreEqual(propTypes[j].DataTypeDefinitionId, result.Groups.ElementAt(i).Properties.ElementAt(j).DataTypeId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -126,6 +126,23 @@ namespace Umbraco.Tests.Models.Mapping
|
||||
Assert.IsTrue(result.Tabs.Except(new[] {result.Tabs.First()}).All(x => x.IsActive == false));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void To_Display_Model_No_Tabs()
|
||||
{
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType();
|
||||
contentType.PropertyGroups.Clear();
|
||||
var content = new Content("Home", -1, contentType) { Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 };
|
||||
|
||||
var result = Mapper.Map<IContent, ContentItemDisplay>(content);
|
||||
|
||||
AssertBasics(result, content);
|
||||
foreach (var p in content.Properties)
|
||||
{
|
||||
AssertDisplayProperty(result, p, ApplicationContext);
|
||||
}
|
||||
Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void To_Display_Model_With_Non_Grouped_Properties()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user