From ea239e61220d993fb1e5adc5f70024a85aae28ac Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Wed, 16 Oct 2013 15:53:56 +0200 Subject: [PATCH 01/27] Missing languages in RTE insert link dialogue Fixes this issue: http://issues.umbraco.org/issue/U4-2551 --- .../umbraco_client/tinymce3/plugins/umbracolink/langs/da_dlg.js | 1 + .../umbraco_client/tinymce3/plugins/umbracolink/langs/de_dlg.js | 1 + .../umbraco_client/tinymce3/plugins/umbracolink/langs/en_dlg.js | 2 +- .../tinymce3/plugins/umbracolink/langs/en_us_dlg.js | 2 +- .../umbraco_client/tinymce3/plugins/umbracolink/langs/fi_dlg.js | 1 + .../umbraco_client/tinymce3/plugins/umbracolink/langs/fr_dlg.js | 1 + .../umbraco_client/tinymce3/plugins/umbracolink/langs/he_dlg.js | 2 +- .../umbraco_client/tinymce3/plugins/umbracolink/langs/it_dlg.js | 1 + .../umbraco_client/tinymce3/plugins/umbracolink/langs/ja_dlg.js | 2 +- .../umbraco_client/tinymce3/plugins/umbracolink/langs/nl_dlg.js | 1 + .../umbraco_client/tinymce3/plugins/umbracolink/langs/no_dlg.js | 1 + .../umbraco_client/tinymce3/plugins/umbracolink/langs/pl_dlg.js | 1 + .../umbraco_client/tinymce3/plugins/umbracolink/langs/pt_dlg.js | 1 + .../umbraco_client/tinymce3/plugins/umbracolink/langs/sv_dlg.js | 2 +- .../umbraco_client/tinymce3/plugins/umbracolink/langs/zh_dlg.js | 2 +- 15 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/da_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/de_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fi_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fr_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/it_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/nl_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/no_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pl_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pt_dlg.js diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/da_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/da_dlg.js new file mode 100644 index 0000000000..06f7fe3d83 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/da_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('da.advlink_dlg',{"target_name":"Destinationsnavn",classes:"Klasser",style:"Stil",id:"Id","popup_position":"Position (X/Y)",langdir:"Sprogretning","popup_size":"St\u00f8rrelse","popup_dependent":"Afh\u00e6ngig (Kun Mozilla/Firefox)","popup_resizable":"Lad det v\u00e6re muligt at \u00e6ndre st\u00f8rrelsen p\u00e5 vinduet","popup_location":"Vis adresselinje","popup_menubar":"Vis menulinje","popup_toolbar":"Vis v\u00e6rkt\u00f8jslinjer","popup_statusbar":"Vis statuslinje","popup_scrollbars":"Vis rullepanel","popup_return":"Inds\u00e6t \'return false\'","popup_name":"Vinduesnavn","popup_url":"Popup URL",popup:"Javascript popup","target_blank":"\u00c5ben i nyt vindue","target_top":"\u00c5ben i \u00f8verste vindue / ramme (erstatter alle rammer)","target_parent":"\u00c5ben i overliggende vindue / ramme","target_same":"\u00c5ben i dette vindue / ramme","anchor_names":"Ankre","popup_opts":"Indstillinger","advanced_props":"Avancerede egenskaber","event_props":"H\u00e6ndelser","popup_props":"Popup egenskaber","general_props":"Generelle egenskaber","advanced_tab":"Advanceret","events_tab":"H\u00e6ndelser","popup_tab":"Popup","general_tab":"Generelt",list:"Liste over links","is_external":"Den URL, der er indtastet, ser ud til at v\u00e6re et eksternt link. Vil du have tilf\u00f8jet det p\u00e5kr\u00e6vede http:// foran?","is_email":"Den URL, der er indtastet, ser ud til at v\u00e6re en emailadresse. Vil du have tilf\u00f8jet det p\u00e5kr\u00e6vede mailto: foran?",titlefield:"Titel",target:"M\u00e5l",url:"Link URL",title:"Inds\u00e6t/rediger link","link_list":"Liste over links",rtl:"H\u00f8jre mod venstre",ltr:"Venstre mod h\u00f8jre",accesskey:"Genvejstast",tabindex:"Tabindex",rev:"Relativ destination til side",rel:"Relativ side til destination",mime:"Destinations-MIME-type",encoding:"Destinationstegns\u00e6t",langcode:"Sprogkode","target_langcode":"Destinationssprog",width:"Bredde",height:"H\u00f8jde"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/de_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/de_dlg.js new file mode 100644 index 0000000000..bb0d3e35b3 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/de_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('de.advlink_dlg',{"target_name":"Name der Zielseite",classes:"Klassen",style:"Format",id:"ID","popup_position":"Position (X/Y)",langdir:"Schriftrichtung","popup_size":"Gr\u00f6\u00dfe","popup_dependent":"Vom Elternfenster abh\u00e4ngig
(nur Mozilla/Firefox) ","popup_resizable":"Vergr\u00f6\u00dfern des Fenster zulassen","popup_location":"Adressleiste anzeigen","popup_menubar":"Browsermen\u00fc anzeigen","popup_toolbar":"Werkzeugleisten anzeigen","popup_statusbar":"Statusleiste anzeigen","popup_scrollbars":"Scrollbalken anzeigen","popup_return":"Link trotz Popup folgen","popup_name":"Name des Fensters","popup_url":"Popup-Adresse",popup:"JavaScript-Popup","target_blank":"In neuem Fenster \u00f6ffnen","target_top":"Im obersten Frame \u00f6ffnen (sprengt das Frameset)","target_parent":"Im \u00fcbergeordneten Fenster/Frame \u00f6ffnen","target_same":"Im selben Fenster/Frame \u00f6ffnen","anchor_names":"Anker","popup_opts":"Optionen","advanced_props":"Erweiterte Eigenschaften","event_props":"Ereignisse","popup_props":"Popup-Eigenschaften","general_props":"Allemeine Eigenschaften","advanced_tab":"Erweitert","events_tab":"Ereignisse","popup_tab":"Popup","general_tab":"Allgemein",list:"Linkliste","is_external":"Diese Adresse scheint ein externer Link zu sein. M\u00f6chten Sie das dazu ben\u00f6tigte \"http://\" voranstellen?","is_email":"Diese Adresse scheint eine E-Mail-Adresse zu sein. M\u00f6chten Sie das dazu ben\u00f6tigte \"mailto:\" voranstellen?",titlefield:"Titel",target:"Fenster",url:"Adresse",title:"Link einf\u00fcgen/bearbeiten","link_list":"Linkliste",rtl:"Rechts nach links",ltr:"Links nach rechts",accesskey:"Tastenk\u00fcrzel",tabindex:"Tabindex",rev:"Beziehung des Linkziels zur Seite",rel:"Beziehung der Seite zum Linkziel",mime:"MIME-Type der Zielseite",encoding:"Zeichenkodierung der Zielseite",langcode:"Sprachcode","target_langcode":"Sprache der Zielseite",width:"Breite",height:"H\u00f6he"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/en_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/en_dlg.js index a11f69cbf5..3169a56580 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/en_dlg.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/en_dlg.js @@ -1 +1 @@ -tinyMCE.addI18n('en.advlink_dlg', { "target_name": "Target Name", classes: "Classes", style: "Style", id: "ID", "popup_position": "Position (X/Y)", langdir: "Language Direction", "popup_size": "Size", "popup_dependent": "Dependent (Mozilla/Firefox Only)", "popup_resizable": "Make Window Resizable", "popup_location": "Show Location Bar", "popup_menubar": "Show Menu Bar", "popup_toolbar": "Show Toolbars", "popup_statusbar": "Show Status Bar", "popup_scrollbars": "Show Scrollbars", "popup_return": "Insert \'return false\'", "popup_name": "Window Name", "popup_url": "Popup URL", popup: "JavaScript Popup", "target_blank": "Open in New Window", "target_top": "Open in Top Frame (Replaces All Frames)", "target_parent": "Open in Parent Window/Frame", "target_same": "Open in This Window/Frame", "anchor_names": "Anchors", "popup_opts": "Options", "advanced_props": "Advanced Properties", "event_props": "Events", "popup_props": "Popup Properties", "general_props": "General Properties", "advanced_tab": "Advanced", "events_tab": "Events", "popup_tab": "Popup", "general_tab": "General", list: "Link List", "is_external": "The URL you entered seems to be an external link. Do you want to add the required http:// prefix?", "is_email": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?", titlefield: "Title", target: "Target", url: "Link URL", title: "Insert/Edit Link", "link_list": "Link List", rtl: "Right to Left", ltr: "Left to Right", accesskey: "AccessKey", tabindex: "TabIndex", rev: "Relationship Target to Page", rel: "Relationship Page to Target", mime: "Target MIME Type", encoding: "Target Character Encoding", langcode: "Language Code", "target_langcode": "Target Language", width: "Width", height: "Height" }); \ No newline at end of file +tinyMCE.addI18n('en.advlink_dlg',{"target_name":"Target Name",classes:"Classes",style:"Style",id:"ID","popup_position":"Position (X/Y)",langdir:"Language Direction","popup_size":"Size","popup_dependent":"Dependent (Mozilla/Firefox Only)","popup_resizable":"Make Window Resizable","popup_location":"Show Location Bar","popup_menubar":"Show Menu Bar","popup_toolbar":"Show Toolbars","popup_statusbar":"Show Status Bar","popup_scrollbars":"Show Scrollbars","popup_return":"Insert \'return false\'","popup_name":"Window Name","popup_url":"Popup URL",popup:"JavaScript Popup","target_blank":"Open in New Window","target_top":"Open in Top Frame (Replaces All Frames)","target_parent":"Open in Parent Window/Frame","target_same":"Open in This Window/Frame","anchor_names":"Anchors","popup_opts":"Options","advanced_props":"Advanced Properties","event_props":"Events","popup_props":"Popup Properties","general_props":"General Properties","advanced_tab":"Advanced","events_tab":"Events","popup_tab":"Popup","general_tab":"General",list:"Link List","is_external":"The URL you entered seems to be an external link. Do you want to add the required http:// prefix?","is_email":"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?",titlefield:"Title",target:"Target",url:"Link URL",title:"Insert/Edit Link","link_list":"Link List",rtl:"Right to Left",ltr:"Left to Right",accesskey:"AccessKey",tabindex:"TabIndex",rev:"Relationship Target to Page",rel:"Relationship Page to Target",mime:"Target MIME Type",encoding:"Target Character Encoding",langcode:"Language Code","target_langcode":"Target Language",width:"Width",height:"Height"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/en_us_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/en_us_dlg.js index 65703ee223..2112e7ce3a 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/en_us_dlg.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/en_us_dlg.js @@ -1 +1 @@ -tinyMCE.addI18n('en_us.advlink_dlg', { "target_name": "Target Name", classes: "Classes", style: "Style", id: "ID", "popup_position": "Position (X/Y)", langdir: "Language Direction", "popup_size": "Size", "popup_dependent": "Dependent (Mozilla/Firefox Only)", "popup_resizable": "Make Window Resizable", "popup_location": "Show Location Bar", "popup_menubar": "Show Menu Bar", "popup_toolbar": "Show Toolbars", "popup_statusbar": "Show Status Bar", "popup_scrollbars": "Show Scrollbars", "popup_return": "Insert \'return false\'", "popup_name": "Window Name", "popup_url": "Popup URL", popup: "JavaScript Popup", "target_blank": "Open in New Window", "target_top": "Open in Top Frame (Replaces All Frames)", "target_parent": "Open in Parent Window/Frame", "target_same": "Open in This Window/Frame", "anchor_names": "Anchors", "popup_opts": "Options", "advanced_props": "Advanced Properties", "event_props": "Events", "popup_props": "Popup Properties", "general_props": "General Properties", "advanced_tab": "Advanced", "events_tab": "Events", "popup_tab": "Popup", "general_tab": "General", list: "Link List", "is_external": "The URL you entered seems to be an external link. Do you want to add the required http:// prefix?", "is_email": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?", titlefield: "Title", target: "Target", url: "Link URL", title: "Insert/Edit Link", "link_list": "Link List", rtl: "Right to Left", ltr: "Left to Right", accesskey: "AccessKey", tabindex: "TabIndex", rev: "Relationship Target to Page", rel: "Relationship Page to Target", mime: "Target MIME Type", encoding: "Target Character Encoding", langcode: "Language Code", "target_langcode": "Target Language", width: "Width", height: "Height" }); \ No newline at end of file +tinyMCE.addI18n('en_us.advlink_dlg',{"target_name":"Target Name",classes:"Classes",style:"Style",id:"ID","popup_position":"Position (X/Y)",langdir:"Language Direction","popup_size":"Size","popup_dependent":"Dependent (Mozilla/Firefox Only)","popup_resizable":"Make Window Resizable","popup_location":"Show Location Bar","popup_menubar":"Show Menu Bar","popup_toolbar":"Show Toolbars","popup_statusbar":"Show Status Bar","popup_scrollbars":"Show Scrollbars","popup_return":"Insert \'return false\'","popup_name":"Window Name","popup_url":"Popup URL",popup:"JavaScript Popup","target_blank":"Open in New Window","target_top":"Open in Top Frame (Replaces All Frames)","target_parent":"Open in Parent Window/Frame","target_same":"Open in This Window/Frame","anchor_names":"Anchors","popup_opts":"Options","advanced_props":"Advanced Properties","event_props":"Events","popup_props":"Popup Properties","general_props":"General Properties","advanced_tab":"Advanced","events_tab":"Events","popup_tab":"Popup","general_tab":"General",list:"Link List","is_external":"The URL you entered seems to be an external link. Do you want to add the required http:// prefix?","is_email":"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?",titlefield:"Title",target:"Target",url:"Link URL",title:"Insert/Edit Link","link_list":"Link List",rtl:"Right to Left",ltr:"Left to Right",accesskey:"AccessKey",tabindex:"TabIndex",rev:"Relationship Target to Page",rel:"Relationship Page to Target",mime:"Target MIME Type",encoding:"Target Character Encoding",langcode:"Language Code","target_langcode":"Target Language",width:"Width",height:"Height"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fi_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fi_dlg.js new file mode 100644 index 0000000000..e49488e733 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fi_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('fi.advlink_dlg',{"target_name":"Kohteen nimi",classes:"Luokat",style:"Tyyli",id:"Id","popup_position":"Sijainti (X/Y)",langdir:"Kielen suunta","popup_size":"Koko","popup_dependent":"Riippuvainen (vain Mozilla/Firefox)","popup_resizable":"Tee ikkunan koko muokattavaksi","popup_location":"N\u00e4yt\u00e4 sijaintipalkki","popup_menubar":"N\u00e4yt\u00e4 valikkopalkki","popup_toolbar":"N\u00e4yt\u00e4 ty\u00f6kalut","popup_statusbar":"N\u00e4yt\u00e4 tilapalkki","popup_scrollbars":"N\u00e4yt\u00e4 vierityspalkit","popup_return":"Lis\u00e4\u00e4 \'return false\'","popup_name":"Ikkunan nimi","popup_url":"Ponnahdusikkunan URL",popup:"JavaScript-ponnahdusikkuna","target_blank":"Avaa uudessa ikkunassa","target_top":"Avaa ylimm\u00e4ss\u00e4 ruudussa (korvaa kaikki ruudut)","target_parent":"Avaa ylemm\u00e4ss\u00e4 ikkunassa","target_same":"Avaa t\u00e4ss\u00e4 ikkunassa","anchor_names":"Ankkurit","popup_opts":"Valinta","advanced_props":"Edistyneet asetukset","event_props":"Tapahtumat (events)","popup_props":"Ponnahdusikkunan asetukset","general_props":"Yleiset asetukset","advanced_tab":"Edistynyt","events_tab":"Tapahtumat","popup_tab":"Ponnahdusikkuna","general_tab":"Yleiset",list:"Linkkilista","is_external":"Sy\u00f6tt\u00e4m\u00e4si URL n\u00e4ytt\u00e4\u00e4 olevan sivuston ulkoinen osoite, haluatko lis\u00e4t\u00e4 http://-etuliitteen?","is_email":"Sy\u00f6tt\u00e4m\u00e4si URL n\u00e4ytt\u00e4\u00e4 olevan s\u00e4hk\u00f6postiosoite, haluatko lis\u00e4t\u00e4 mailto:-etuliitteen?",titlefield:"Otsikko",target:"Kohde (target)",url:"Linkin URL",title:"Lis\u00e4\u00e4/muokkaa linkki\u00e4","link_list":"Linkkilista",rtl:"Oikealta vasemmalle",ltr:"Vasemmalta oikealle",accesskey:"Pikan\u00e4pp\u00e4in",tabindex:"Tabulaattori-indeksi",rev:"Kohteen suhde sivuun",rel:"Sivun suhde kohteeseen",mime:"Kohteen MIME-tyyppi",encoding:"Kohteen merkist\u00f6koodaus",langcode:"Kielen koodi","target_langcode":"Kohteen kieli",width:"Leveys",height:"Korkeus"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fr_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fr_dlg.js new file mode 100644 index 0000000000..38e5a7858f --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fr_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('fr.advlink_dlg',{"target_name":"Nom de la cible",classes:"Classes",style:"Style",id:"Id","popup_position":"Position (X/Y)",langdir:"Sens de lecture","popup_size":"Taille","popup_dependent":"D\u00e9pendante (seulement sous Mozilla/Firefox)","popup_resizable":"Autoriser le redimensionnement de la fen\u00eatre","popup_location":"Afficher la barre d\'adresse","popup_menubar":"Afficher la barre de menu","popup_toolbar":"Afficher la barre d\'outils","popup_statusbar":"Afficher la barre d\'\u00e9tat","popup_scrollbars":"Afficher les ascenseurs","popup_return":"Ins\u00e9rer \'return false\'","popup_name":"Nom de la fen\u00eatre","popup_url":"URL de la popup",popup:"Popup Javascript","target_blank":"Ouvrir dans une nouvelle fen\u00eatre","target_top":"Ouvrir dans le cadre principal (remplace tous les cadres)","target_parent":"Ouvrir dans la fen\u00eatre / le cadre parent","target_same":"Ouvrir dans cette fen\u00eatre / dans ce cadre","anchor_names":"Ancres","popup_opts":"Options","advanced_props":"Propri\u00e9t\u00e9s avanc\u00e9es","event_props":"\u00c9v\u00e8nements","popup_props":"Propri\u00e9t\u00e9s de la popup","general_props":"Propri\u00e9t\u00e9s g\u00e9n\u00e9rales","advanced_tab":"Avanc\u00e9","events_tab":"\u00c9v\u00e8nements","popup_tab":"Popup","general_tab":"G\u00e9n\u00e9ral",list:"Liste de liens","is_external":"L\'URL que vous avez saisie semble \u00eatre une adresse web externe. Souhaitez-vous ajouter le pr\u00e9fixe \u00ab http:// \u00bb ?","is_email":"L\'URL que vous avez saisie semble \u00eatre une adresse e-mail, souhaitez-vous ajouter le pr\u00e9fixe \u00ab mailto: \u00bb ?",titlefield:"Titre",target:"Cible",url:"URL du lien",title:"Ins\u00e9rer / \u00e9diter un lien","link_list":"Liste des liens",rtl:"Droite \u00e0 gauche",ltr:"Gauche \u00e0 droite",accesskey:"Touche d\'acc\u00e8s rapide",tabindex:"Tabindex",rev:"Relation de la cible \u00e0 la page",rel:"Relation de la page \u00e0 la cible",mime:"Type MIME de la cible",encoding:"Encodage de la cible",langcode:"Code de la langue","target_langcode":"Langue de la cible",width:"Largeur",height:"Hauteur"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/he_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/he_dlg.js index d0ef8b987b..7ea21bdaae 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/he_dlg.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/he_dlg.js @@ -1 +1 @@ -tinyMCE.addI18n('he.advlink_dlg', { "target_name": "Target Name", classes: "Classes", style: "Style", id: "ID", "popup_position": "Position (X/Y)", langdir: "Language Direction", "popup_size": "Size", "popup_dependent": "Dependent (Mozilla/Firefox Only)", "popup_resizable": "Make Window Resizable", "popup_location": "Show Location Bar", "popup_menubar": "Show Menu Bar", "popup_toolbar": "Show Toolbars", "popup_statusbar": "Show Status Bar", "popup_scrollbars": "Show Scrollbars", "popup_return": "Insert \'return false\'", "popup_name": "Window Name", "popup_url": "Popup URL", popup: "JavaScript Popup", "target_blank": "Open in New Window", "target_top": "Open in Top Frame (Replaces All Frames)", "target_parent": "Open in Parent Window/Frame", "target_same": "Open in This Window/Frame", "anchor_names": "Anchors", "popup_opts": "Options", "advanced_props": "Advanced Properties", "event_props": "Events", "popup_props": "Popup Properties", "general_props": "General Properties", "advanced_tab": "Advanced", "events_tab": "Events", "popup_tab": "Popup", "general_tab": "General", list: "Link List", "is_external": "The URL you entered seems to be an external link. Do you want to add the required http:// prefix?", "is_email": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?", titlefield: "Title", target: "Target", url: "Link URL", title: "Insert/Edit Link", "link_list": "Link List", rtl: "Right to Left", ltr: "Left to Right", accesskey: "AccessKey", tabindex: "TabIndex", rev: "Relationship Target to Page", rel: "Relationship Page to Target", mime: "Target MIME Type", encoding: "Target Character Encoding", langcode: "Language Code", "target_langcode": "Target Language", width: "Width", height: "Height" }); \ No newline at end of file +tinyMCE.addI18n('he.advlink_dlg',{"target_name":"Target name",classes:"Classes",style:"\u05e1\u05d2\u05e0\u05d5\u05df",id:"\u05de\u05e1\u05e4\u05e8 \u05e1\u05d9\u05d3\u05d5\u05e8\u05d9","popup_position":"\u05de\u05d9\u05e7\u05d5\u05dd (X/Y)",langdir:"\u05db\u05d9\u05d5\u05d5\u05df \u05d4\u05e9\u05e4\u05d4","popup_size":"\u05d2\u05d5\u05d3\u05dc","popup_dependent":"Dependent (Mozilla/Firefox only)","popup_resizable":"\u05d7\u05dc\u05d5\u05df \u05d3\u05d9\u05e0\u05d0\u05de\u05d9(resizable)","popup_location":"\u05d4\u05e6\u05d2\u05ea location bar ","popup_menubar":"\u05d4\u05e6\u05d2\u05ea \u05ea\u05e4\u05e8\u05d9\u05d8","popup_toolbar":"\u05d4\u05e6\u05d2\u05ea \u05e1\u05e8\u05d2\u05dc\u05d9 \u05db\u05dc\u05d9\u05dd","popup_statusbar":"\u05d4\u05e6\u05d2\u05ea \u05e9\u05d5\u05e8\u05ea \u05e1\u05d8\u05d0\u05d8\u05d5\u05e1","popup_scrollbars":"\u05d4\u05e6\u05d2\u05ea \u05e4\u05e1 \u05d2\u05dc\u05d9\u05dc\u05d4","popup_return":"\u05d9\u05e9 \u05dc\u05d4\u05db\u05e0\u05d9\u05e1 \'return false\'","popup_name":"\u05e9\u05dd \u05d4\u05d7\u05dc\u05d5\u05df","popup_url":"\u05d7\u05dc\u05d5\u05df \u05de\u05d5\u05e7\u05e4\u05e5 URL",popup:"\u05d7\u05dc\u05d5\u05df \u05de\u05d5\u05e7\u05e4\u05e5 javascript","target_blank":"\u05e4\u05ea\u05d9\u05d7\u05d4 \u05d1\u05d7\u05dc\u05d5\u05df \u05d7\u05d3\u05e9","target_top":"\u05e4\u05ea\u05d9\u05d7\u05d4 \u05d1\u05d7\u05dc\u05d5\u05df \u05d4\u05d1\u05df \u05d4\u05e8\u05d0\u05e9\u05d9(\u05de\u05d7\u05dc\u05d9\u05e3 \u05d0\u05ea \u05db\u05dc \u05d7\u05dc\u05d5\u05e0\u05d5\u05ea \u05d4\u05d1\u05e0\u05d9\u05dd)","target_parent":"\u05e4\u05ea\u05d9\u05d7\u05d4 \u05d1\u05dc\u05d5\u05df \u05d4\u05d0\u05d1\u05d0/\u05d7\u05dc\u05d5\u05df \u05d1\u05df","target_same":"\u05e4\u05ea\u05d9\u05d7\u05d4 \u05d1\u05d7\u05dc\u05d5\u05df \u05d7\u05d3\u05e9/\u05d7\u05dc\u05d5\u05df \u05d1\u05df","anchor_names":"\u05e7\u05d9\u05e9\u05d5\u05e8 \u05dc\u05e1\u05d9\u05de\u05e0\u05d9\u05d4","popup_opts":"\u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea","advanced_props":"\u05ea\u05db\u05d5\u05e0\u05d5\u05ea \u05de\u05ea\u05e7\u05d3\u05de\u05d5\u05ea","event_props":"\u05de\u05d0\u05d5\u05e8\u05e2\u05d5\u05ea","popup_props":"\u05ea\u05db\u05d5\u05e0\u05d5\u05ea \u05d7\u05dc\u05d5\u05df \u05de\u05d5\u05e7\u05e4\u05e5","general_props":"\u05ea\u05db\u05d5\u05e0\u05d5\u05ea \u05db\u05dc\u05dc\u05d9\u05d5\u05ea","advanced_tab":"\u05de\u05ea\u05e7\u05d3\u05dd","events_tab":"\u05d0\u05e8\u05d5\u05e2\u05d9\u05dd","popup_tab":"\u05d7\u05dc\u05d5\u05df \u05de\u05d5\u05e7\u05e4\u05e5","general_tab":"\u05db\u05dc\u05dc\u05d9",list:"\u05e8\u05e9\u05d9\u05de\u05ea \u05e7\u05d9\u05e9\u05d5\u05e8\u05d9\u05dd","is_external":"\u05db\u05ea\u05d5\u05d1\u05ea \u05d4-URL \u05e9\u05d4\u05d5\u05db\u05e0\u05e1\u05d4 \u05d4\u05d9\u05d0 \u05db\u05db\u05dc \u05d4\u05e0\u05e8\u05d0\u05d4 \u05e7\u05d9\u05e9\u05d5\u05e8 \u05d7\u05d9\u05e6\u05d5\u05e0\u05d9 \u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d5\u05e1\u05d9\u05e3 \u05d0\u05ea \u05d4\u05e7\u05d9\u05d3\u05d5\u05de\u05ea http:// \u05d4\u05e0\u05d3\u05e8\u05e9\u05ea?","is_email":"\u05db\u05ea\u05d5\u05d1\u05ea \u05d4-URL \u05e9\u05d4\u05d5\u05db\u05e0\u05e1\u05d4 \u05d4\u05d9\u05d0 \u05db\u05db\u05dc \u05d4\u05e0\u05e8\u05d0\u05d4 \u05db\u05ea\u05d5\u05d1\u05ea \u05de\u05d9\u05d9\u05dc \u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d5\u05e1\u05d9\u05e3 \u05d0\u05ea \u05d4\u05e7\u05d9\u05d3\u05d5\u05de\u05ea MAILTO \u05d4\u05e0\u05d3\u05e8\u05e9\u05ea?",titlefield:"\u05db\u05d5\u05ea\u05e8\u05ea \u05d4\u05e7\u05d9\u05e9\u05d5\u05e8",target:"\u05d9\u05e2\u05d3",url:"\u05db\u05ea\u05d5\u05d1\u05ea \u05d4\u05e7\u05d9\u05e9\u05d5\u05e8",title:"\u05d4\u05d5\u05e1\u05e4\u05d4/\u05e2\u05e8\u05d9\u05db\u05ea \u05e7\u05d9\u05e9\u05d5\u05e8","link_list":"\u05e8\u05e9\u05d9\u05de\u05ea \u05e7\u05d9\u05e9\u05d5\u05e8\u05d9\u05dd",rtl:"\u05de\u05d9\u05de\u05d9\u05df \u05dc\u05e9\u05de\u05d0\u05dc",ltr:"\u05de\u05e9\u05de\u05d0\u05dc \u05dc\u05d9\u05de\u05d9\u05df",accesskey:"Accesskey",tabindex:"Tabindex",rev:"Relationship target to page",rel:"Relationship page to target",mime:"Target MIME type",encoding:"Target character encoding",langcode:"\u05e7\u05d5\u05d3 \u05d4\u05e9\u05e4\u05d4","target_langcode":"Target language",width:"\u05e8\u05d5\u05d7\u05d1",height:"\u05d2\u05d5\u05d1\u05d4"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/it_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/it_dlg.js new file mode 100644 index 0000000000..bf19659d05 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/it_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('it.advlink_dlg',{"target_name":"Nome target",classes:"Classe",style:"Stile",id:"Id","popup_position":"Posizione (X/Y)",langdir:"Direzione del testo","popup_size":"Dimensioni","popup_dependent":"Dipendente (Solo in Mozilla/Firefox)","popup_resizable":"Rendi la finestra ridimensionabile","popup_location":"Mostra barra navigazione","popup_menubar":"Mostra barra menu","popup_toolbar":"Mostra barre strumenti","popup_statusbar":"Mostra barra di stato","popup_scrollbars":"Mostra barre di scorrimento","popup_return":"Inserisci \'return false\'","popup_name":"Nome finestra","popup_url":"URL Popup",popup:"Popup Javascript","target_blank":"Apri in una nuova finestra","target_top":"Apri nella cornice superiore (sostituisce tutte le cornici)","target_parent":"Apri nella finestra / cornice genitore","target_same":"Apri in questa finestra / cornice","anchor_names":"Ancore","popup_opts":"Opzioni","advanced_props":"Propriet\u00e0 avanzate","event_props":"Eventi","popup_props":"Propriet\u00e0 popup","general_props":"Propriet\u00e0 generali","advanced_tab":"Avanzate","events_tab":"Eventi","popup_tab":"Popup","general_tab":"Generale",list:"Lista collegamenti","is_external":"L\'URL inserito sembra essere un link esterno. Aggiungere il necessario prefisso http:// ?","is_email":"L\'URL inserito sembra essere un indirizzo email. Aggiungere il necessario prefisso mailto: ?",titlefield:"Titolo",target:"Target",url:"URL collegamento",title:"Inserisci/modifica link","link_list":"Lista collegamenti",rtl:"Destra verso sinistra",ltr:"Sinistra verso destra",accesskey:"Carattere di accesso",tabindex:"Indice tabulazione",rev:"Relazione da target a pagina",rel:"Relazione da pagina a target",mime:"Tipo MIME del target",encoding:"Codifica carattere del target",langcode:"Lingua","target_langcode":"Lingua del target",width:"Larghezza",height:"Altezza"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/ja_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/ja_dlg.js index 91df8efc11..68ebcd2e6e 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/ja_dlg.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/ja_dlg.js @@ -1 +1 @@ -tinyMCE.addI18n('ja.advlink_dlg',{"target_name":"ターゲットの名前",classes:"クラス",style:"スタイル",id:"ID","popup_position":"位置 (X/Y)",langdir:"文章の方向","popup_size":"大きさ","popup_dependent":"依存(MozillaとFirefoxだけ)","popup_resizable":"ウインドウのサイズ変更を許可","popup_location":"アドレスバーを表示","popup_menubar":"メニューバーを表示","popup_toolbar":"ツールバーを表示","popup_statusbar":"ステータスバーを表示","popup_scrollbars":"スクロールバーを表示","popup_return":"\'return false\'を挿入","popup_name":"ウインドウの名前","popup_url":"ポップアップのURL",popup:"Javascriptポップアップ","target_blank":"新しいウインドウで開く","target_top":"トップのフレームで開く(すべてのフレームを置き換え)","target_parent":"親ウインドウ/親フレームで開く","target_same":"このウインドウ/フレームで開く","anchor_names":"アンカー","popup_opts":"オプション","advanced_props":"高度な属性","event_props":"イベント","popup_props":"ポップアップ","general_props":"一般","advanced_tab":"専門的","events_tab":"イベント","popup_tab":"ポップアップ","general_tab":"一般",list:"リンクの一覧","is_external":"入力したURLは外部のリンクのようです。リンクに http:// を追加しますか?","is_email":"入力したURLは電子メールアドレスのようです。リンクに mailto: を追加しますか?",titlefield:"タイトル",target:"ターゲット",url:"リンクのURL",title:"リンクの挿入/編集","link_list":"リンクの一覧",rtl:"右から左",ltr:"左から右",accesskey:"アクセスキー",tabindex:"タブインデックス",rev:"ターゲットからページの関係",rel:"ページからターゲットの関係",mime:"ターゲットのMIMEタイプ",encoding:"ターゲットの文字エンコーディング",langcode:"言語コード","target_langcode":"ターゲットの言語",width:"幅",height:"高さ"}); \ No newline at end of file +tinyMCE.addI18n('ja.advlink_dlg',{"target_name":"\u30bf\u30fc\u30b2\u30c3\u30c8\u306e\u540d\u524d",classes:"\u30af\u30e9\u30b9",style:"\u30b9\u30bf\u30a4\u30eb",id:"ID","popup_position":"\u4f4d\u7f6e (X/Y)",langdir:"\u6587\u7ae0\u306e\u65b9\u5411","popup_size":"\u5927\u304d\u3055","popup_dependent":"\u4f9d\u5b58(Mozilla\u3068Firefox\u3060\u3051)","popup_resizable":"\u30a6\u30a4\u30f3\u30c9\u30a6\u306e\u30b5\u30a4\u30ba\u5909\u66f4\u3092\u8a31\u53ef","popup_location":"\u30a2\u30c9\u30ec\u30b9\u30d0\u30fc\u3092\u8868\u793a","popup_menubar":"\u30e1\u30cb\u30e5\u30fc\u30d0\u30fc\u3092\u8868\u793a","popup_toolbar":"\u30c4\u30fc\u30eb\u30d0\u30fc\u3092\u8868\u793a","popup_statusbar":"\u30b9\u30c6\u30fc\u30bf\u30b9\u30d0\u30fc\u3092\u8868\u793a","popup_scrollbars":"\u30b9\u30af\u30ed\u30fc\u30eb\u30d0\u30fc\u3092\u8868\u793a","popup_return":"\'return false\'\u3092\u633f\u5165","popup_name":"\u30a6\u30a4\u30f3\u30c9\u30a6\u306e\u540d\u524d","popup_url":"\u30dd\u30c3\u30d7\u30a2\u30c3\u30d7\u306eURL",popup:"Javascript\u30dd\u30c3\u30d7\u30a2\u30c3\u30d7","target_blank":"\u65b0\u3057\u3044\u30a6\u30a4\u30f3\u30c9\u30a6\u3067\u958b\u304f","target_top":"\u30c8\u30c3\u30d7\u306e\u30d5\u30ec\u30fc\u30e0\u3067\u958b\u304f(\u3059\u3079\u3066\u306e\u30d5\u30ec\u30fc\u30e0\u3092\u7f6e\u304d\u63db\u3048)","target_parent":"\u89aa\u30a6\u30a4\u30f3\u30c9\u30a6/\u89aa\u30d5\u30ec\u30fc\u30e0\u3067\u958b\u304f","target_same":"\u3053\u306e\u30a6\u30a4\u30f3\u30c9\u30a6/\u30d5\u30ec\u30fc\u30e0\u3067\u958b\u304f","anchor_names":"\u30a2\u30f3\u30ab\u30fc","popup_opts":"\u30aa\u30d7\u30b7\u30e7\u30f3","advanced_props":"\u9ad8\u5ea6\u306a\u5c5e\u6027","event_props":"\u30a4\u30d9\u30f3\u30c8","popup_props":"\u30dd\u30c3\u30d7\u30a2\u30c3\u30d7","general_props":"\u4e00\u822c","advanced_tab":"\u5c02\u9580\u7684","events_tab":"\u30a4\u30d9\u30f3\u30c8","popup_tab":"\u30dd\u30c3\u30d7\u30a2\u30c3\u30d7","general_tab":"\u4e00\u822c",list:"\u30ea\u30f3\u30af\u306e\u4e00\u89a7","is_external":"\u5165\u529b\u3057\u305fURL\u306f\u5916\u90e8\u306e\u30ea\u30f3\u30af\u306e\u3088\u3046\u3067\u3059\u3002\u30ea\u30f3\u30af\u306b http:// \u3092\u8ffd\u52a0\u3057\u307e\u3059\u304b?","is_email":"\u5165\u529b\u3057\u305fURL\u306f\u96fb\u5b50\u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b9\u306e\u3088\u3046\u3067\u3059\u3002\u30ea\u30f3\u30af\u306b mailto: \u3092\u8ffd\u52a0\u3057\u307e\u3059\u304b?",titlefield:"\u30bf\u30a4\u30c8\u30eb",target:"\u30bf\u30fc\u30b2\u30c3\u30c8",url:"\u30ea\u30f3\u30af\u306eURL",title:"\u30ea\u30f3\u30af\u306e\u633f\u5165/\u7de8\u96c6","link_list":"\u30ea\u30f3\u30af\u306e\u4e00\u89a7",rtl:"\u53f3\u304b\u3089\u5de6",ltr:"\u5de6\u304b\u3089\u53f3",accesskey:"\u30a2\u30af\u30bb\u30b9\u30ad\u30fc",tabindex:"\u30bf\u30d6\u30a4\u30f3\u30c7\u30c3\u30af\u30b9",rev:"\u30bf\u30fc\u30b2\u30c3\u30c8\u304b\u3089\u30da\u30fc\u30b8\u306e\u95a2\u4fc2",rel:"\u30da\u30fc\u30b8\u304b\u3089\u30bf\u30fc\u30b2\u30c3\u30c8\u306e\u95a2\u4fc2",mime:"\u30bf\u30fc\u30b2\u30c3\u30c8\u306eMIME\u30bf\u30a4\u30d7",encoding:"\u30bf\u30fc\u30b2\u30c3\u30c8\u306e\u6587\u5b57\u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0",langcode:"\u8a00\u8a9e\u30b3\u30fc\u30c9","target_langcode":"\u30bf\u30fc\u30b2\u30c3\u30c8\u306e\u8a00\u8a9e",width:"\u5e45",height:"\u9ad8\u3055"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/nl_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/nl_dlg.js new file mode 100644 index 0000000000..b2924758b0 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/nl_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('nl.advlink_dlg',{"target_name":"Doel",classes:"Klasses",style:"Stijl",id:"Id","popup_position":"Positie (X/Y)",langdir:"Taalrichting","popup_size":"Grootte","popup_dependent":"Afhankelijk (Alleen Mozilla/Firefox)","popup_resizable":"Aanpasbaar venster","popup_location":"Lokatiebalk weergeven","popup_menubar":"Menubalk weergeven","popup_toolbar":"Werkbalk weergeven","popup_statusbar":"Statusbalk weergeven","popup_scrollbars":"Scrollbalken weergeven","popup_return":"\'return false\' invoegen","popup_name":"Vensternaam","popup_url":"Popup URL",popup:"Javascript popup","target_blank":"In nieuw venster openen","target_top":"In bovenste frame openen (vervangt gehele pagina)","target_parent":"In bovenliggend venster / frame openen","target_same":"In dit venster / frame openen","anchor_names":"Ankers","popup_opts":"Opties","advanced_props":"Geavanceerde eigenschappen","event_props":"Gebeurtenissen","popup_props":"Popup eigenschappen","general_props":"Algemene eigenschappen","advanced_tab":"Geavanceerd","events_tab":"Gebeurtenissen","popup_tab":"Popup","general_tab":"Algemeen",list:"Lijst","is_external":"De ingevoerde URL lijkt op een externe link. Wilt u de vereiste http:// tekst voorvoegen?","is_email":"De ingevoerde URL lijkt op een e-mailadres. Wilt u de vereiste mailto: tekst voorvoegen?",titlefield:"Titel",target:"Doel",url:"URL",title:"Link invoegen/bewerken","link_list":"Lijst",rtl:"Van rechts naar links",ltr:"Van links naar rechts",accesskey:"Toegangstoets",tabindex:"Tabvolgorde",rev:"Relatie van doel tot pagina",rel:"Relatie van pagina tot doel",mime:"MIME type",encoding:"Taalcodering",langcode:"Taalcode","target_langcode":"Taal",width:"Breedte",height:"Hoogte"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/no_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/no_dlg.js new file mode 100644 index 0000000000..1a333095d3 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/no_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('no.advlink_dlg',{"target_name":"M\u00e5lnavn",classes:"Klasse",style:"Stil",id:"Id","popup_position":"Posisjon (X/Y)",langdir:"Skriftretning","popup_size":"St\u00f8rrelse","popup_dependent":"Avhengig vindu (kun i Mozilla/Firefox)","popup_resizable":"Gj\u00f8r vinduet skalerbart","popup_location":"Vis plasseringslinje","popup_menubar":"Vis menylinje","popup_toolbar":"Vis verkt\u00f8ylinjer","popup_statusbar":"Vis statusline","popup_scrollbars":"Vis rullefelt","popup_return":"Sett inn \\\'return false\\\'","popup_name":"Navn p\u00e5 vindu","popup_url":"Popup URL",popup:"Javascript popup","target_blank":"\u00c5pne i nytt vindu","target_top":"\u00c5pne i toppvindu (erstatter alle rammer)","target_parent":"\u00c5pne i overordnet vindu/ramme","target_same":"\u00c5pne i samme vindu/ramme","anchor_names":"Anker","popup_opts":"Innstillinger","advanced_props":"Avanserte egenskaper","event_props":"Hendelser","popup_props":"Popupegenskaper","general_props":"Generelle egenskaper","advanced_tab":"Avansert","events_tab":"Hendelser","popup_tab":"Popup","general_tab":"Generelt",list:"Liste over lenker","is_external":"URLen du skrev inn ser ut til \u00e5 v\u00e6re en ekstern lenke. \u00d8nsker du \u00e5 legge til obligatorisk http://-prefiks?","is_email":"URLen du skrev inn ser ut til \u00e5 v\u00e6re Epost adresse. \u00d8nsker du \u00e5 legge til obligatorisk mailto:-prefiks?",titlefield:"Tittel",target:"M\u00e5l",url:"Lenke URL",title:"Sett inn / rediger lenke","link_list":"Liste over lenker",rtl:"H\u00f8yre mot venstre",ltr:"Venstre mot h\u00f8yre",accesskey:"Hurtigtast",tabindex:"Tabulatorindeks",rev:"Forholdet mellom m\u00e5l og side",rel:"Forholdet mellom side og m\u00e5l",mime:"M\u00e5l MIME type",encoding:"M\u00e5l karakter koding",langcode:"Spr\u00e5kkode","target_langcode":"M\u00e5lspr\u00e5k",width:"Bredde",height:"H\u00f8yde"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pl_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pl_dlg.js new file mode 100644 index 0000000000..d529d7ad17 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pl_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('pl.advlink_dlg',{"target_name":"Nazwa celu",classes:"Klasy",style:"Styl",id:"Id","popup_position":"Pozycja (X/Y)",langdir:"Kierunek czytania tekstu","popup_size":"Rozmiar","popup_dependent":"Zale\u017cny (Mozilla/Firefox wy\u0142\u0105cznie)","popup_resizable":"Stw\u00f3rz okno z mo\u017cliwo\u015bci\u0105 zmiany rozmiaru","popup_location":"Poka\u017c pasek adresu","popup_menubar":"Poka\u017c pasek menu","popup_toolbar":"Poka\u017c narz\u0119dzia","popup_statusbar":"Poka\u017c pasek statusu","popup_scrollbars":"Poka\u017c paski przewijania","popup_return":"Wstaw \'return false\'","popup_name":"Nazwa okna","popup_url":"URL okna",popup:"Wyskakuj\u0105ce okno","target_blank":"Otw\u00f3rz w nowym oknie","target_top":"Otw\u00f3rz w g\u00f3rnej ramce (zamie\u0144 wszystkie ramki)","target_parent":"Otw\u00f3rz w nadrz\u0119dnym oknie / ramce","target_same":"Otw\u00f3rz w tym oknie / ramce","anchor_names":"Kotwice","popup_opts":"Opcje","advanced_props":"Zaawansowae w\u0142a\u015bciwo\u015bci","event_props":"Zdarzenia","popup_props":"W\u0142a\u015bciwo\u015bci okna","general_props":"W\u0142a\u015bciwo\u015bci og\u00f3lne","advanced_tab":"Zaawansowane","events_tab":"Zdarzenia","popup_tab":"Popup","general_tab":"Og\u00f3lne",list:"Lista link\u00f3w","is_external":"Podany adres wydaje si\u0119 by\u0107 zewn\u0119trznym linkiem, czy chcesz doda\u0107 wymagany prefiks http://?","is_email":"Podany adres wydaje si\u0119 by\u0107 adresem emailowym, czy chcesz doda\u0107 wymagany prefiks mailto:?",titlefield:"Tytu\u0142",target:"Cel",url:"URL linka",title:"Wstaw/edytuj link","link_list":"Lista odno\u015bnik\u00f3w",rtl:"Kierunek z prawej do lewej",ltr:"Kierunek z lewej do prawej",accesskey:"Klawisz skr\u00f3tu",tabindex:"Numer tab",rev:"Relacje celu do strony",rel:"Relacje strony do celu",mime:"Docelowy typ MIME",encoding:"Kodowanie znak\u00f3w celu",langcode:"Kod j\u0119zyka","target_langcode":"Docelowy kod j\u0119zyka",width:"Szeroko\u015b\u0107",height:"Wysoko\u015b\u0107"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pt_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pt_dlg.js new file mode 100644 index 0000000000..8167855442 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pt_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('pt.advlink_dlg',{"target_name":"Nome do alvo",classes:"Classes",style:"Estilo",id:"Id","popup_position":"Posi\u00e7\u00e3o (X/Y)",langdir:"Dire\u00e7\u00e3o do texto","popup_size":"Tamanho","popup_dependent":"Dependente (Mozilla/Firefox apenas)","popup_resizable":"Permitir altera\u00e7\u00e3o do tamanho da janela","popup_location":"Mostrar a barra de endere\u00e7os","popup_menubar":"Mostrar a barra de menu","popup_toolbar":"Mostrar a barra de ferramentas","popup_statusbar":"Mostrar a barra de status","popup_scrollbars":"Mostrar as barras de scroll","popup_return":"Inserir \"return false\"","popup_name":"Nome da janela","popup_url":"URL do popup",popup:"Popup javascript","target_blank":"Abrir numa nova janela","target_top":"Abrir na p\u00e1gina inteira (substitui todos os quadros)","target_parent":"Abrir na janela/quadro pai","target_same":"Abrir nesta janela/quadro","anchor_names":"\u00c2ncoras","popup_opts":"Op\u00e7\u00f5es","advanced_props":"Propriedades avan\u00e7adas","event_props":"Eventos","popup_props":"Propriedades de popup","general_props":"Propriedades gerais","advanced_tab":"Avan\u00e7ado","events_tab":"Eventos","popup_tab":"Popup","general_tab":"Geral",list:"Lista de hyperlinks","is_external":"A URL digitada parece conduzir a um link externo. Deseja acrescentar o prefixo necess\u00e1rio http://?","is_email":"A URL digitada parece ser um endere\u00e7o de e-mail. Deseja acrescentar o prefixo necess\u00e1rio mailto:?",titlefield:"T\u00edtulo",target:"Alvo",url:"URL do hyperlink",title:"Inserir/editar hyperlink","link_list":"Lista de hyperlinks",rtl:"Da direita para a esquerda",ltr:"Da esquerda para a direita",accesskey:"Chave de acesso",tabindex:"Tabindex",rev:"Rela\u00e7\u00e3o alvo/p\u00e1gina",rel:"Rela\u00e7\u00e3o p\u00e1gina/alvo",mime:"Tipo MIME alvo",encoding:"Codifica\u00e7\u00e3o de caracteres",langcode:"C\u00f3digo do idioma","target_langcode":"Idioma alvo",width:"Largura",height:"Altura"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/sv_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/sv_dlg.js index 0d665f99e1..8a6194472a 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/sv_dlg.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/sv_dlg.js @@ -1 +1 @@ -tinyMCE.addI18n('sv.advlink_dlg', { "target_name": "Målets namn", classes: "Klasser", style: "Stil", id: "ID", "popup_position": "Position (X/Y)", langdir: "Språkmål", "popup_size": "Storlek", "popup_dependent": "Dependent (Mozilla/Firefox Only)", "popup_resizable": "Make Window Resizable", "popup_location": "Show Location Bar", "popup_menubar": "Show Menu Bar", "popup_toolbar": "Show Toolbars", "popup_statusbar": "Show Status Bar", "popup_scrollbars": "Show Scrollbars", "popup_return": "Insert \'return false\'", "popup_name": "Window Name", "popup_url": "Popup URL", popup: "JavaScript Popup", "target_blank": "Öppna i ett nytt fönster", "target_top": "Öppna i toppramen", "target_parent": "Öppna i nuvarande fönster/ram", "target_same": "Öppna i detta fönster/ram", "anchor_names": "Ankare", "popup_opts": "Options", "advanced_props": "Advanced Properties", "event_props": "Events", "popup_props": "Popup Properties", "general_props": "General Properties", "advanced_tab": "Advanced", "events_tab": "Events", "popup_tab": "Popup", "general_tab": "General", list: "Link List", "is_external": "The URL you entered seems to be an external link. Do you want to add the required http:// prefix?", "is_email": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?", titlefield: "Title", target: "Mål", url: "Länkens URL", title: "Infoga/redigera länk", "link_list": "Link List", rtl: "Right to Left", ltr: "Left to Right", accesskey: "AccessKey", tabindex: "TabIndex", rev: "Relationship Target to Page", rel: "Relationship Page to Target", mime: "Target MIME Type", encoding: "Target Character Encoding", langcode: "Språkkod", "target_langcode": "Målspråk", width: "Bredd", height: "Höjd" }); \ No newline at end of file +tinyMCE.addI18n('sv.advlink_dlg',{"target_name":"M\u00e5lnamn",classes:"Klasser",style:"Stil",id:"Id","popup_position":"Position (x/y)",langdir:"Skriftriktning","popup_size":"Storlek","popup_dependent":"Beroende av (Mozilla/Firefox enbart)","popup_resizable":"Skalbart f\u00f6nster","popup_location":"Adressraden","popup_menubar":"Menyrad","popup_toolbar":"Verktygsf\u00e4lt","popup_statusbar":"Statusf\u00e4lt","popup_scrollbars":"Rullningslister","popup_return":"Infoga \'return false\'","popup_name":"F\u00f6nsternamn","popup_url":"Popup URL",popup:"Javascript popup","target_blank":"\u00d6ppna i nytt f\u00f6nster","target_top":"\u00d6ppna i toppramen (ers\u00e4tter alla ramar)","target_parent":"\u00d6ppna i \u00f6verliggande f\u00f6nster/ram","target_same":"\u00d6ppna i detta f\u00f6nster/ram","anchor_names":"Bokm\u00e4rken","popup_opts":"Inst\u00e4llningar","advanced_props":"Avancerade inst\u00e4llningar","event_props":"H\u00e4ndelser","popup_props":"Popup-inst\u00e4llningar","general_props":"Generella inst\u00e4llningar","advanced_tab":"Avancerat","events_tab":"H\u00e4ndelser","popup_tab":"Popup","general_tab":"Generellt",list:"L\u00e4nklista","is_external":"L\u00e4nken du angav verkar vara en extern adress. Vill du infoga http:// prefixet p\u00e5 l\u00e4nken?","is_email":"L\u00e4nken du angav verkar vara en e-post adress. Vill du infoga mailto: prefixet p\u00e5 l\u00e4nken?",titlefield:"Titel",target:"M\u00e5l",url:"L\u00e4nkens URL",title:"Infoga/redigera l\u00e4nk","link_list":"L\u00e4nklista",rtl:"H\u00f6ger till v\u00e4nster",ltr:"V\u00e4nster till h\u00f6ger",accesskey:"Snabbtangent",tabindex:"Tabbindex",rev:"Omv\u00e4nd relation (rev)",rel:"Relation (rel attribut)",mime:"MIME type",encoding:"Teckenformattering",langcode:"Spr\u00e5kkod","target_langcode":"M\u00e5lspr\u00e5k",width:"Bredd",height:"H\u00f6jd"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/zh_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/zh_dlg.js index bec669b5f2..fb228f5942 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/zh_dlg.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/zh_dlg.js @@ -1 +1 @@ -tinyMCE.addI18n('zh.advlink_dlg', { "target_name": "目标名称", classes: "类", style: "样式", id: "ID", "popup_position": "位置 (X/Y)", langdir: "语言书写方向", "popup_size": "尺寸", "popup_dependent": "依赖 (仅限Mozilla/Firefox)", "popup_resizable": "窗口大小可调", "popup_location": "显示地址栏", "popup_menubar": "显示菜单栏", "popup_toolbar": "显示工具栏", "popup_statusbar": "显示状态栏", "popup_scrollbars": "显示滚动条", "popup_return": "插入 \'return false\'", "popup_name": "窗口名称", "popup_url": "URL", popup: "JavaScript 弹出窗口", "target_blank": "在新窗口中打开", "target_top": "在顶部位置打开(替换掉所有Frames)", "target_parent": "在父级窗口或位置中打开", "target_same": "在该窗口或位置打开", "anchor_names": "锚点", "popup_opts": "选项", "advanced_props": "高级属性", "event_props": "事件", "popup_props": "弹窗属性", "general_props": "普通属性", "advanced_tab": "高级", "events_tab": "事件", "popup_tab": "弹窗", "general_tab": "普通", list: "链接列表", "is_external": "您输入的好像是外部链接,您是否想在前面添加http://?", "is_email": "您输入的好像是邮箱地址,您是否想在前面添加mailto:?", titlefield: "标题", target: "目标", url: "链接URL", title: "插入/编辑链接", "link_list": "链接列表", rtl: "从右到左", ltr: "从左到右", accesskey: "访问键", tabindex: "Tab索引", rev: "目标至页面关系", rel: "页面至目标关系", mime: "目标MIME类型", encoding: "目标语言编码", langcode: "语言代码", "target_langcode": "目标语言", width: "宽", height: "高" }); \ No newline at end of file +tinyMCE.addI18n('zh-cn.advlink_dlg',{"target_name":"\u76ee\u6807\u540d\u79f0",classes:"\u7c7b\u522b",style:"\u6837\u5f0f",id:"ID","popup_position":"\u4f4d\u7f6e(X/Y)",langdir:"\u8bed\u8a00\u4e66\u5199\u65b9\u5411","popup_size":"\u5927\u5c0f","popup_dependent":"\u9650\u5236(\u4ec5\u652f\u6301Mozilla/Firefox)","popup_resizable":"\u7a97\u53e3\u53ef\u8c03\u6574\u5927\u5c0f","popup_location":"\u663e\u793a\u5730\u5740\u680f","popup_menubar":"\u663e\u793a\u83dc\u5355\u680f","popup_toolbar":"\u663e\u793a\u5de5\u5177\u680f","popup_statusbar":"\u663e\u793a\u72b6\u6001\u680f","popup_scrollbars":"\u663e\u793a\u6eda\u52a8\u6761","popup_return":"\u63d2\u5165\'return false\'","popup_name":"\u7a97\u53e3\u540d\u79f0","popup_url":"\u5f39\u51faURL",popup:"Javascript\u5f39\u7a97","target_blank":"\u5728\u65b0\u7a97\u53e3\u6253\u5f00","target_top":"\u5728\u9876\u90e8\u6846\u67b6\u6253\u5f00\uff08\u91cd\u7f6e\u6240\u6709\u6846\u67b6\uff09","target_parent":"\u5728\u7236\u7a97\u53e3/\u6846\u67b6\u6253\u5f00","target_same":"\u5728\u5f53\u524d\u7a97\u53e3/\u6846\u67b6\u6253\u5f00","anchor_names":"\u951a","popup_opts":"\u9009\u9879","advanced_props":"\u9ad8\u7ea7\u5c5e\u6027","event_props":"\u4e8b\u4ef6","popup_props":"\u5f39\u51fa\u5c5e\u6027","general_props":"\u666e\u901a\u5c5e\u6027","advanced_tab":"\u9ad8\u7ea7","events_tab":"\u4e8b\u4ef6","popup_tab":"\u5f39\u51fa","general_tab":"\u666e\u901a",list:"\u94fe\u63a5\u5217\u8868","is_external":"\u60a8\u8f93\u5165\u7684URL\u662f\u4e00\u4e2a\u5916\u90e8\u94fe\u63a5\uff0c\u662f\u5426\u8981\u52a0\u4e0a\"http://\"\u524d\u7f00\uff1f","is_email":"\u60a8\u8f93\u5165URL\u662f\u7535\u5b50\u90ae\u4ef6\u5730\u5740\uff0c\u662f\u5426\u9700\u8981\u52a0\"mailto:\"\u524d\u7f00\uff1f",titlefield:"\u6807\u9898",target:"\u6253\u5f00\u65b9\u5f0f",url:"\u8d85\u94fe\u63a5URL",title:"\u63d2\u5165/\u7f16\u8f91 \u8d85\u94fe\u63a5","link_list":"\u94fe\u63a5\u5217\u8868",rtl:"\u4ece\u53f3\u5230\u5de6",ltr:"\u4ece\u5de6\u5230\u53f3",accesskey:"\u5feb\u6377\u952e",tabindex:"Tab\u7d22\u5f15",rev:"\u76ee\u6807\u5230\u7f51\u9875\u7684\u5173\u7cfb",rel:"\u7f51\u9875\u5230\u76ee\u6807\u7684\u5173\u7cfb",mime:"\u76ee\u6807MIME\u7c7b\u578b",encoding:"\u76ee\u6807\u8bed\u8a00\u7f16\u7801",langcode:"\u8bed\u8a00\u7f16\u7801","target_langcode":"\u76ee\u6807\u8bed\u8a00",width:"Width",height:"Height"}); \ No newline at end of file From 3abd4df8bd27631a797b8108228cca2180e55e19 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Wed, 16 Oct 2013 16:01:17 +0200 Subject: [PATCH 02/27] Enabled lists plugin in tinymce by default Fixes http://issues.umbraco.org/issue/U4-3023 --- src/Umbraco.Web.UI/config/tinyMceConfig.Release.config | 1 + src/Umbraco.Web.UI/config/tinyMceConfig.config | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI/config/tinyMceConfig.Release.config b/src/Umbraco.Web.UI/config/tinyMceConfig.Release.config index ae2aa171f9..579dc77ba1 100644 --- a/src/Umbraco.Web.UI/config/tinyMceConfig.Release.config +++ b/src/Umbraco.Web.UI/config/tinyMceConfig.Release.config @@ -208,6 +208,7 @@ inlinepopups noneditable table + lists umbracomacro umbracoimg advlink diff --git a/src/Umbraco.Web.UI/config/tinyMceConfig.config b/src/Umbraco.Web.UI/config/tinyMceConfig.config index 0a3982b343..2c3f2484f6 100644 --- a/src/Umbraco.Web.UI/config/tinyMceConfig.config +++ b/src/Umbraco.Web.UI/config/tinyMceConfig.config @@ -211,6 +211,7 @@ inlinepopups noneditable table + lists umbracomacro advlink From 451d5eb4843d321d70a541ad96748a3a3a4262b9 Mon Sep 17 00:00:00 2001 From: mkariti Date: Thu, 7 Nov 2013 14:40:26 +0200 Subject: [PATCH 03/27] U4-415 --- .../tinymce3/themes/umbraco/skins/umbraco/content.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/themes/umbraco/skins/umbraco/content.css b/src/Umbraco.Web.UI/umbraco_client/tinymce3/themes/umbraco/skins/umbraco/content.css index adb3483d5c..4cc9238ea7 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/themes/umbraco/skins/umbraco/content.css +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/themes/umbraco/skins/umbraco/content.css @@ -1,4 +1,4 @@ -body, td, pre {color:#000; font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px; margin:8px;} +body, td, pre {color:#000; font-family:Verdana, Arial, Helvetica, sans-serif; font-size:12px; margin:8px;} body {background:#FFF;} body.mceForceColors {background:#FFF; color:#000;} h1 {font-size: 2em} From 02e167c1f678720e5b82ae696e675ee30ca50e1f Mon Sep 17 00:00:00 2001 From: Tim Payne Date: Thu, 7 Nov 2013 14:20:47 +0000 Subject: [PATCH 04/27] Fixes issue U4-3063 where copying a document type uses the rename for the node alias, but not the title, resulting in two Doctypes with the same name, but different aliases, rather than different names AND aliases. --- .../umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs index ad8410da85..8987635447 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs @@ -146,8 +146,9 @@ namespace umbraco.dialogs var contentType = contentTypeService.GetContentType( int.Parse(Request.GetItemAsString("id"))); - var alias = rename.Text.Replace("'", "''"); + var alias = rename.Text.Trim().Replace("'", "''"); var clone = ((Umbraco.Core.Models.ContentType) contentType).Clone(alias); + clone.Name = rename.Text.Trim(); contentTypeService.Save(clone); var returnUrl = string.Format("{0}/settings/editNodeTypeNew.aspx?id={1}", SystemDirectories.Umbraco, clone.Id); From 7cd01fc50b4d744e8760995184fce07c3afc9439 Mon Sep 17 00:00:00 2001 From: mattbrailsford Date: Thu, 7 Nov 2013 14:35:44 +0000 Subject: [PATCH 05/27] Added ValidateRequest="false" on package installer page to allow user controls which postback html / code. --- src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx b/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx index e271bf7413..d9d9896b30 100644 --- a/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx +++ b/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx @@ -1,6 +1,6 @@ <%@ Page Language="c#" MasterPageFile="../../masterpages/umbracoPage.Master" CodeBehind="installer.aspx.cs" AutoEventWireup="True" Inherits="umbraco.presentation.developer.packages.Installer" - Trace="false" %> + Trace="false" ValidateRequest="false" %> <%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> From 516beedbbbedc3a0c3815a5293a6ac4d4639fc39 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 7 Nov 2013 14:48:20 +0000 Subject: [PATCH 06/27] Fixes: U4-3452 Update the MIT License URL (Packager) --- src/umbraco.cms/businesslogic/Packager/data.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/umbraco.cms/businesslogic/Packager/data.cs b/src/umbraco.cms/businesslogic/Packager/data.cs index 6f0df33b4d..a950212c47 100644 --- a/src/umbraco.cms/businesslogic/Packager/data.cs +++ b/src/umbraco.cms/businesslogic/Packager/data.cs @@ -116,8 +116,8 @@ namespace umbraco.cms.businesslogic.packager instance.Attributes.Append(xmlHelper.addAttribute(Source, "skinRepoGuid", "")); XmlElement license = Source.CreateElement("license"); - license.InnerText = "MIT license"; - license.Attributes.Append(xmlHelper.addAttribute(Source, "url", "http://www.opensource.org/licenses/mit-license.php")); + license.InnerText = "MIT License"; + license.Attributes.Append(xmlHelper.addAttribute(Source, "url", "http://opensource.org/licenses/MIT")); instance.AppendChild(license); XmlElement author = Source.CreateElement("author"); From a5782a40afe0cc862c02657c42f95042cf882348 Mon Sep 17 00:00:00 2001 From: dipunm Date: Thu, 7 Nov 2013 14:59:00 +0000 Subject: [PATCH 07/27] Missing languages in RTE insert link dialog. Also included files in csproj file to ensure proper build. --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 9 +++++++++ .../tinymce3/plugins/umbracolink/langs/da_dlg.js | 1 + .../tinymce3/plugins/umbracolink/langs/de_dlg.js | 1 + .../tinymce3/plugins/umbracolink/langs/fi_dlg.js | 1 + .../tinymce3/plugins/umbracolink/langs/fr_dlg.js | 1 + .../tinymce3/plugins/umbracolink/langs/it_dlg.js | 1 + .../tinymce3/plugins/umbracolink/langs/nl_dlg.js | 1 + .../tinymce3/plugins/umbracolink/langs/no_dlg.js | 1 + .../tinymce3/plugins/umbracolink/langs/pl_dlg.js | 1 + .../tinymce3/plugins/umbracolink/langs/pt_dlg.js | 1 + 10 files changed, 18 insertions(+) create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/da_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/de_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fi_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fr_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/it_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/nl_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/no_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pl_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pt_dlg.js diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index d717813eb2..dfcfcb9e2c 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -1337,10 +1337,19 @@ + + + + + + + + + diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/da_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/da_dlg.js new file mode 100644 index 0000000000..06f7fe3d83 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/da_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('da.advlink_dlg',{"target_name":"Destinationsnavn",classes:"Klasser",style:"Stil",id:"Id","popup_position":"Position (X/Y)",langdir:"Sprogretning","popup_size":"St\u00f8rrelse","popup_dependent":"Afh\u00e6ngig (Kun Mozilla/Firefox)","popup_resizable":"Lad det v\u00e6re muligt at \u00e6ndre st\u00f8rrelsen p\u00e5 vinduet","popup_location":"Vis adresselinje","popup_menubar":"Vis menulinje","popup_toolbar":"Vis v\u00e6rkt\u00f8jslinjer","popup_statusbar":"Vis statuslinje","popup_scrollbars":"Vis rullepanel","popup_return":"Inds\u00e6t \'return false\'","popup_name":"Vinduesnavn","popup_url":"Popup URL",popup:"Javascript popup","target_blank":"\u00c5ben i nyt vindue","target_top":"\u00c5ben i \u00f8verste vindue / ramme (erstatter alle rammer)","target_parent":"\u00c5ben i overliggende vindue / ramme","target_same":"\u00c5ben i dette vindue / ramme","anchor_names":"Ankre","popup_opts":"Indstillinger","advanced_props":"Avancerede egenskaber","event_props":"H\u00e6ndelser","popup_props":"Popup egenskaber","general_props":"Generelle egenskaber","advanced_tab":"Advanceret","events_tab":"H\u00e6ndelser","popup_tab":"Popup","general_tab":"Generelt",list:"Liste over links","is_external":"Den URL, der er indtastet, ser ud til at v\u00e6re et eksternt link. Vil du have tilf\u00f8jet det p\u00e5kr\u00e6vede http:// foran?","is_email":"Den URL, der er indtastet, ser ud til at v\u00e6re en emailadresse. Vil du have tilf\u00f8jet det p\u00e5kr\u00e6vede mailto: foran?",titlefield:"Titel",target:"M\u00e5l",url:"Link URL",title:"Inds\u00e6t/rediger link","link_list":"Liste over links",rtl:"H\u00f8jre mod venstre",ltr:"Venstre mod h\u00f8jre",accesskey:"Genvejstast",tabindex:"Tabindex",rev:"Relativ destination til side",rel:"Relativ side til destination",mime:"Destinations-MIME-type",encoding:"Destinationstegns\u00e6t",langcode:"Sprogkode","target_langcode":"Destinationssprog",width:"Bredde",height:"H\u00f8jde"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/de_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/de_dlg.js new file mode 100644 index 0000000000..bb0d3e35b3 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/de_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('de.advlink_dlg',{"target_name":"Name der Zielseite",classes:"Klassen",style:"Format",id:"ID","popup_position":"Position (X/Y)",langdir:"Schriftrichtung","popup_size":"Gr\u00f6\u00dfe","popup_dependent":"Vom Elternfenster abh\u00e4ngig
(nur Mozilla/Firefox) ","popup_resizable":"Vergr\u00f6\u00dfern des Fenster zulassen","popup_location":"Adressleiste anzeigen","popup_menubar":"Browsermen\u00fc anzeigen","popup_toolbar":"Werkzeugleisten anzeigen","popup_statusbar":"Statusleiste anzeigen","popup_scrollbars":"Scrollbalken anzeigen","popup_return":"Link trotz Popup folgen","popup_name":"Name des Fensters","popup_url":"Popup-Adresse",popup:"JavaScript-Popup","target_blank":"In neuem Fenster \u00f6ffnen","target_top":"Im obersten Frame \u00f6ffnen (sprengt das Frameset)","target_parent":"Im \u00fcbergeordneten Fenster/Frame \u00f6ffnen","target_same":"Im selben Fenster/Frame \u00f6ffnen","anchor_names":"Anker","popup_opts":"Optionen","advanced_props":"Erweiterte Eigenschaften","event_props":"Ereignisse","popup_props":"Popup-Eigenschaften","general_props":"Allemeine Eigenschaften","advanced_tab":"Erweitert","events_tab":"Ereignisse","popup_tab":"Popup","general_tab":"Allgemein",list:"Linkliste","is_external":"Diese Adresse scheint ein externer Link zu sein. M\u00f6chten Sie das dazu ben\u00f6tigte \"http://\" voranstellen?","is_email":"Diese Adresse scheint eine E-Mail-Adresse zu sein. M\u00f6chten Sie das dazu ben\u00f6tigte \"mailto:\" voranstellen?",titlefield:"Titel",target:"Fenster",url:"Adresse",title:"Link einf\u00fcgen/bearbeiten","link_list":"Linkliste",rtl:"Rechts nach links",ltr:"Links nach rechts",accesskey:"Tastenk\u00fcrzel",tabindex:"Tabindex",rev:"Beziehung des Linkziels zur Seite",rel:"Beziehung der Seite zum Linkziel",mime:"MIME-Type der Zielseite",encoding:"Zeichenkodierung der Zielseite",langcode:"Sprachcode","target_langcode":"Sprache der Zielseite",width:"Breite",height:"H\u00f6he"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fi_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fi_dlg.js new file mode 100644 index 0000000000..e49488e733 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fi_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('fi.advlink_dlg',{"target_name":"Kohteen nimi",classes:"Luokat",style:"Tyyli",id:"Id","popup_position":"Sijainti (X/Y)",langdir:"Kielen suunta","popup_size":"Koko","popup_dependent":"Riippuvainen (vain Mozilla/Firefox)","popup_resizable":"Tee ikkunan koko muokattavaksi","popup_location":"N\u00e4yt\u00e4 sijaintipalkki","popup_menubar":"N\u00e4yt\u00e4 valikkopalkki","popup_toolbar":"N\u00e4yt\u00e4 ty\u00f6kalut","popup_statusbar":"N\u00e4yt\u00e4 tilapalkki","popup_scrollbars":"N\u00e4yt\u00e4 vierityspalkit","popup_return":"Lis\u00e4\u00e4 \'return false\'","popup_name":"Ikkunan nimi","popup_url":"Ponnahdusikkunan URL",popup:"JavaScript-ponnahdusikkuna","target_blank":"Avaa uudessa ikkunassa","target_top":"Avaa ylimm\u00e4ss\u00e4 ruudussa (korvaa kaikki ruudut)","target_parent":"Avaa ylemm\u00e4ss\u00e4 ikkunassa","target_same":"Avaa t\u00e4ss\u00e4 ikkunassa","anchor_names":"Ankkurit","popup_opts":"Valinta","advanced_props":"Edistyneet asetukset","event_props":"Tapahtumat (events)","popup_props":"Ponnahdusikkunan asetukset","general_props":"Yleiset asetukset","advanced_tab":"Edistynyt","events_tab":"Tapahtumat","popup_tab":"Ponnahdusikkuna","general_tab":"Yleiset",list:"Linkkilista","is_external":"Sy\u00f6tt\u00e4m\u00e4si URL n\u00e4ytt\u00e4\u00e4 olevan sivuston ulkoinen osoite, haluatko lis\u00e4t\u00e4 http://-etuliitteen?","is_email":"Sy\u00f6tt\u00e4m\u00e4si URL n\u00e4ytt\u00e4\u00e4 olevan s\u00e4hk\u00f6postiosoite, haluatko lis\u00e4t\u00e4 mailto:-etuliitteen?",titlefield:"Otsikko",target:"Kohde (target)",url:"Linkin URL",title:"Lis\u00e4\u00e4/muokkaa linkki\u00e4","link_list":"Linkkilista",rtl:"Oikealta vasemmalle",ltr:"Vasemmalta oikealle",accesskey:"Pikan\u00e4pp\u00e4in",tabindex:"Tabulaattori-indeksi",rev:"Kohteen suhde sivuun",rel:"Sivun suhde kohteeseen",mime:"Kohteen MIME-tyyppi",encoding:"Kohteen merkist\u00f6koodaus",langcode:"Kielen koodi","target_langcode":"Kohteen kieli",width:"Leveys",height:"Korkeus"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fr_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fr_dlg.js new file mode 100644 index 0000000000..38e5a7858f --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fr_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('fr.advlink_dlg',{"target_name":"Nom de la cible",classes:"Classes",style:"Style",id:"Id","popup_position":"Position (X/Y)",langdir:"Sens de lecture","popup_size":"Taille","popup_dependent":"D\u00e9pendante (seulement sous Mozilla/Firefox)","popup_resizable":"Autoriser le redimensionnement de la fen\u00eatre","popup_location":"Afficher la barre d\'adresse","popup_menubar":"Afficher la barre de menu","popup_toolbar":"Afficher la barre d\'outils","popup_statusbar":"Afficher la barre d\'\u00e9tat","popup_scrollbars":"Afficher les ascenseurs","popup_return":"Ins\u00e9rer \'return false\'","popup_name":"Nom de la fen\u00eatre","popup_url":"URL de la popup",popup:"Popup Javascript","target_blank":"Ouvrir dans une nouvelle fen\u00eatre","target_top":"Ouvrir dans le cadre principal (remplace tous les cadres)","target_parent":"Ouvrir dans la fen\u00eatre / le cadre parent","target_same":"Ouvrir dans cette fen\u00eatre / dans ce cadre","anchor_names":"Ancres","popup_opts":"Options","advanced_props":"Propri\u00e9t\u00e9s avanc\u00e9es","event_props":"\u00c9v\u00e8nements","popup_props":"Propri\u00e9t\u00e9s de la popup","general_props":"Propri\u00e9t\u00e9s g\u00e9n\u00e9rales","advanced_tab":"Avanc\u00e9","events_tab":"\u00c9v\u00e8nements","popup_tab":"Popup","general_tab":"G\u00e9n\u00e9ral",list:"Liste de liens","is_external":"L\'URL que vous avez saisie semble \u00eatre une adresse web externe. Souhaitez-vous ajouter le pr\u00e9fixe \u00ab http:// \u00bb ?","is_email":"L\'URL que vous avez saisie semble \u00eatre une adresse e-mail, souhaitez-vous ajouter le pr\u00e9fixe \u00ab mailto: \u00bb ?",titlefield:"Titre",target:"Cible",url:"URL du lien",title:"Ins\u00e9rer / \u00e9diter un lien","link_list":"Liste des liens",rtl:"Droite \u00e0 gauche",ltr:"Gauche \u00e0 droite",accesskey:"Touche d\'acc\u00e8s rapide",tabindex:"Tabindex",rev:"Relation de la cible \u00e0 la page",rel:"Relation de la page \u00e0 la cible",mime:"Type MIME de la cible",encoding:"Encodage de la cible",langcode:"Code de la langue","target_langcode":"Langue de la cible",width:"Largeur",height:"Hauteur"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/it_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/it_dlg.js new file mode 100644 index 0000000000..bf19659d05 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/it_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('it.advlink_dlg',{"target_name":"Nome target",classes:"Classe",style:"Stile",id:"Id","popup_position":"Posizione (X/Y)",langdir:"Direzione del testo","popup_size":"Dimensioni","popup_dependent":"Dipendente (Solo in Mozilla/Firefox)","popup_resizable":"Rendi la finestra ridimensionabile","popup_location":"Mostra barra navigazione","popup_menubar":"Mostra barra menu","popup_toolbar":"Mostra barre strumenti","popup_statusbar":"Mostra barra di stato","popup_scrollbars":"Mostra barre di scorrimento","popup_return":"Inserisci \'return false\'","popup_name":"Nome finestra","popup_url":"URL Popup",popup:"Popup Javascript","target_blank":"Apri in una nuova finestra","target_top":"Apri nella cornice superiore (sostituisce tutte le cornici)","target_parent":"Apri nella finestra / cornice genitore","target_same":"Apri in questa finestra / cornice","anchor_names":"Ancore","popup_opts":"Opzioni","advanced_props":"Propriet\u00e0 avanzate","event_props":"Eventi","popup_props":"Propriet\u00e0 popup","general_props":"Propriet\u00e0 generali","advanced_tab":"Avanzate","events_tab":"Eventi","popup_tab":"Popup","general_tab":"Generale",list:"Lista collegamenti","is_external":"L\'URL inserito sembra essere un link esterno. Aggiungere il necessario prefisso http:// ?","is_email":"L\'URL inserito sembra essere un indirizzo email. Aggiungere il necessario prefisso mailto: ?",titlefield:"Titolo",target:"Target",url:"URL collegamento",title:"Inserisci/modifica link","link_list":"Lista collegamenti",rtl:"Destra verso sinistra",ltr:"Sinistra verso destra",accesskey:"Carattere di accesso",tabindex:"Indice tabulazione",rev:"Relazione da target a pagina",rel:"Relazione da pagina a target",mime:"Tipo MIME del target",encoding:"Codifica carattere del target",langcode:"Lingua","target_langcode":"Lingua del target",width:"Larghezza",height:"Altezza"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/nl_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/nl_dlg.js new file mode 100644 index 0000000000..b2924758b0 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/nl_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('nl.advlink_dlg',{"target_name":"Doel",classes:"Klasses",style:"Stijl",id:"Id","popup_position":"Positie (X/Y)",langdir:"Taalrichting","popup_size":"Grootte","popup_dependent":"Afhankelijk (Alleen Mozilla/Firefox)","popup_resizable":"Aanpasbaar venster","popup_location":"Lokatiebalk weergeven","popup_menubar":"Menubalk weergeven","popup_toolbar":"Werkbalk weergeven","popup_statusbar":"Statusbalk weergeven","popup_scrollbars":"Scrollbalken weergeven","popup_return":"\'return false\' invoegen","popup_name":"Vensternaam","popup_url":"Popup URL",popup:"Javascript popup","target_blank":"In nieuw venster openen","target_top":"In bovenste frame openen (vervangt gehele pagina)","target_parent":"In bovenliggend venster / frame openen","target_same":"In dit venster / frame openen","anchor_names":"Ankers","popup_opts":"Opties","advanced_props":"Geavanceerde eigenschappen","event_props":"Gebeurtenissen","popup_props":"Popup eigenschappen","general_props":"Algemene eigenschappen","advanced_tab":"Geavanceerd","events_tab":"Gebeurtenissen","popup_tab":"Popup","general_tab":"Algemeen",list:"Lijst","is_external":"De ingevoerde URL lijkt op een externe link. Wilt u de vereiste http:// tekst voorvoegen?","is_email":"De ingevoerde URL lijkt op een e-mailadres. Wilt u de vereiste mailto: tekst voorvoegen?",titlefield:"Titel",target:"Doel",url:"URL",title:"Link invoegen/bewerken","link_list":"Lijst",rtl:"Van rechts naar links",ltr:"Van links naar rechts",accesskey:"Toegangstoets",tabindex:"Tabvolgorde",rev:"Relatie van doel tot pagina",rel:"Relatie van pagina tot doel",mime:"MIME type",encoding:"Taalcodering",langcode:"Taalcode","target_langcode":"Taal",width:"Breedte",height:"Hoogte"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/no_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/no_dlg.js new file mode 100644 index 0000000000..1a333095d3 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/no_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('no.advlink_dlg',{"target_name":"M\u00e5lnavn",classes:"Klasse",style:"Stil",id:"Id","popup_position":"Posisjon (X/Y)",langdir:"Skriftretning","popup_size":"St\u00f8rrelse","popup_dependent":"Avhengig vindu (kun i Mozilla/Firefox)","popup_resizable":"Gj\u00f8r vinduet skalerbart","popup_location":"Vis plasseringslinje","popup_menubar":"Vis menylinje","popup_toolbar":"Vis verkt\u00f8ylinjer","popup_statusbar":"Vis statusline","popup_scrollbars":"Vis rullefelt","popup_return":"Sett inn \\\'return false\\\'","popup_name":"Navn p\u00e5 vindu","popup_url":"Popup URL",popup:"Javascript popup","target_blank":"\u00c5pne i nytt vindu","target_top":"\u00c5pne i toppvindu (erstatter alle rammer)","target_parent":"\u00c5pne i overordnet vindu/ramme","target_same":"\u00c5pne i samme vindu/ramme","anchor_names":"Anker","popup_opts":"Innstillinger","advanced_props":"Avanserte egenskaper","event_props":"Hendelser","popup_props":"Popupegenskaper","general_props":"Generelle egenskaper","advanced_tab":"Avansert","events_tab":"Hendelser","popup_tab":"Popup","general_tab":"Generelt",list:"Liste over lenker","is_external":"URLen du skrev inn ser ut til \u00e5 v\u00e6re en ekstern lenke. \u00d8nsker du \u00e5 legge til obligatorisk http://-prefiks?","is_email":"URLen du skrev inn ser ut til \u00e5 v\u00e6re Epost adresse. \u00d8nsker du \u00e5 legge til obligatorisk mailto:-prefiks?",titlefield:"Tittel",target:"M\u00e5l",url:"Lenke URL",title:"Sett inn / rediger lenke","link_list":"Liste over lenker",rtl:"H\u00f8yre mot venstre",ltr:"Venstre mot h\u00f8yre",accesskey:"Hurtigtast",tabindex:"Tabulatorindeks",rev:"Forholdet mellom m\u00e5l og side",rel:"Forholdet mellom side og m\u00e5l",mime:"M\u00e5l MIME type",encoding:"M\u00e5l karakter koding",langcode:"Spr\u00e5kkode","target_langcode":"M\u00e5lspr\u00e5k",width:"Bredde",height:"H\u00f8yde"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pl_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pl_dlg.js new file mode 100644 index 0000000000..d529d7ad17 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pl_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('pl.advlink_dlg',{"target_name":"Nazwa celu",classes:"Klasy",style:"Styl",id:"Id","popup_position":"Pozycja (X/Y)",langdir:"Kierunek czytania tekstu","popup_size":"Rozmiar","popup_dependent":"Zale\u017cny (Mozilla/Firefox wy\u0142\u0105cznie)","popup_resizable":"Stw\u00f3rz okno z mo\u017cliwo\u015bci\u0105 zmiany rozmiaru","popup_location":"Poka\u017c pasek adresu","popup_menubar":"Poka\u017c pasek menu","popup_toolbar":"Poka\u017c narz\u0119dzia","popup_statusbar":"Poka\u017c pasek statusu","popup_scrollbars":"Poka\u017c paski przewijania","popup_return":"Wstaw \'return false\'","popup_name":"Nazwa okna","popup_url":"URL okna",popup:"Wyskakuj\u0105ce okno","target_blank":"Otw\u00f3rz w nowym oknie","target_top":"Otw\u00f3rz w g\u00f3rnej ramce (zamie\u0144 wszystkie ramki)","target_parent":"Otw\u00f3rz w nadrz\u0119dnym oknie / ramce","target_same":"Otw\u00f3rz w tym oknie / ramce","anchor_names":"Kotwice","popup_opts":"Opcje","advanced_props":"Zaawansowae w\u0142a\u015bciwo\u015bci","event_props":"Zdarzenia","popup_props":"W\u0142a\u015bciwo\u015bci okna","general_props":"W\u0142a\u015bciwo\u015bci og\u00f3lne","advanced_tab":"Zaawansowane","events_tab":"Zdarzenia","popup_tab":"Popup","general_tab":"Og\u00f3lne",list:"Lista link\u00f3w","is_external":"Podany adres wydaje si\u0119 by\u0107 zewn\u0119trznym linkiem, czy chcesz doda\u0107 wymagany prefiks http://?","is_email":"Podany adres wydaje si\u0119 by\u0107 adresem emailowym, czy chcesz doda\u0107 wymagany prefiks mailto:?",titlefield:"Tytu\u0142",target:"Cel",url:"URL linka",title:"Wstaw/edytuj link","link_list":"Lista odno\u015bnik\u00f3w",rtl:"Kierunek z prawej do lewej",ltr:"Kierunek z lewej do prawej",accesskey:"Klawisz skr\u00f3tu",tabindex:"Numer tab",rev:"Relacje celu do strony",rel:"Relacje strony do celu",mime:"Docelowy typ MIME",encoding:"Kodowanie znak\u00f3w celu",langcode:"Kod j\u0119zyka","target_langcode":"Docelowy kod j\u0119zyka",width:"Szeroko\u015b\u0107",height:"Wysoko\u015b\u0107"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pt_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pt_dlg.js new file mode 100644 index 0000000000..8167855442 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pt_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('pt.advlink_dlg',{"target_name":"Nome do alvo",classes:"Classes",style:"Estilo",id:"Id","popup_position":"Posi\u00e7\u00e3o (X/Y)",langdir:"Dire\u00e7\u00e3o do texto","popup_size":"Tamanho","popup_dependent":"Dependente (Mozilla/Firefox apenas)","popup_resizable":"Permitir altera\u00e7\u00e3o do tamanho da janela","popup_location":"Mostrar a barra de endere\u00e7os","popup_menubar":"Mostrar a barra de menu","popup_toolbar":"Mostrar a barra de ferramentas","popup_statusbar":"Mostrar a barra de status","popup_scrollbars":"Mostrar as barras de scroll","popup_return":"Inserir \"return false\"","popup_name":"Nome da janela","popup_url":"URL do popup",popup:"Popup javascript","target_blank":"Abrir numa nova janela","target_top":"Abrir na p\u00e1gina inteira (substitui todos os quadros)","target_parent":"Abrir na janela/quadro pai","target_same":"Abrir nesta janela/quadro","anchor_names":"\u00c2ncoras","popup_opts":"Op\u00e7\u00f5es","advanced_props":"Propriedades avan\u00e7adas","event_props":"Eventos","popup_props":"Propriedades de popup","general_props":"Propriedades gerais","advanced_tab":"Avan\u00e7ado","events_tab":"Eventos","popup_tab":"Popup","general_tab":"Geral",list:"Lista de hyperlinks","is_external":"A URL digitada parece conduzir a um link externo. Deseja acrescentar o prefixo necess\u00e1rio http://?","is_email":"A URL digitada parece ser um endere\u00e7o de e-mail. Deseja acrescentar o prefixo necess\u00e1rio mailto:?",titlefield:"T\u00edtulo",target:"Alvo",url:"URL do hyperlink",title:"Inserir/editar hyperlink","link_list":"Lista de hyperlinks",rtl:"Da direita para a esquerda",ltr:"Da esquerda para a direita",accesskey:"Chave de acesso",tabindex:"Tabindex",rev:"Rela\u00e7\u00e3o alvo/p\u00e1gina",rel:"Rela\u00e7\u00e3o p\u00e1gina/alvo",mime:"Tipo MIME alvo",encoding:"Codifica\u00e7\u00e3o de caracteres",langcode:"C\u00f3digo do idioma","target_langcode":"Idioma alvo",width:"Largura",height:"Altura"}); \ No newline at end of file From f16aa38fff1f0be192b3a5961c2486caa46dd89a Mon Sep 17 00:00:00 2001 From: Tim Payne Date: Thu, 7 Nov 2013 15:51:58 +0000 Subject: [PATCH 08/27] Fixes issue U4-2757 where generic properties are not ordered correctly. --- .../umbraco.presentation/umbraco/controls/ContentControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentControl.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentControl.cs index 589682a18c..f334110c8f 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentControl.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentControl.cs @@ -178,7 +178,7 @@ namespace umbraco.controls //if the property is not in a tab, add it to the general tab var props = _content.GenericProperties; - foreach (Property p in props) + foreach (Property p in props.OrderBy(x => x.PropertyType.SortOrder)) { if (inTab[p.PropertyType.Id.ToString()] == null) AddControlNew(p, tpProp, ui.Text("general", "properties", null)); From 347d41c8414588221c8dd847c730454a7dff348a Mon Sep 17 00:00:00 2001 From: Tim Payne Date: Thu, 7 Nov 2013 16:06:16 +0000 Subject: [PATCH 09/27] Fixes issue U4-2760, and makes the installed packages list by name in alphabetical order to make it easier to see what's installed. --- .../umbraco.presentation/umbraco/Trees/loadPackages.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackages.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackages.cs index cbdc3f0a90..cfd3777ba0 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackages.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackages.cs @@ -55,7 +55,7 @@ namespace umbraco .OrderByDescending(x => Version.TryParse(x.Data.Version, out v) ? v : new Version()) .GroupBy(x => x.Data.Name) .Select(x => x.First()) - .OrderBy(x => x.Data.Id); + .OrderBy(x => x.Data.Name); foreach (var p in uniquePackages) { var xNode = XmlTreeNode.Create(this); From d963917556663bef4da155381c0db2248f010aca Mon Sep 17 00:00:00 2001 From: mattbrailsford Date: Thu, 7 Nov 2013 16:30:23 +0000 Subject: [PATCH 10/27] Added SortOrder to XML for document types (used by export) Added ability for doctype importer to set the sort order if a sort order element is available --- src/Umbraco.Core/Services/PackagingService.cs | 9 +++++++++ src/umbraco.cms/businesslogic/web/DocumentType.cs | 1 + 2 files changed, 10 insertions(+) diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index efa6ae9fe6..c33a0235f4 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -586,9 +586,18 @@ namespace Umbraco.Core.Services { var id = tab.Element("Id").Value;//Do we need to use this for tracking? var caption = tab.Element("Caption").Value; + if (contentType.PropertyGroups.Contains(caption) == false) { contentType.AddPropertyGroup(caption); + + int sortOrder; + if(tab.Element("SortOrder") != null && + int.TryParse(tab.Element("SortOrder").Value, out sortOrder)) + { + // Override the sort order with the imported value + contentType.PropertyGroups[caption].SortOrder = sortOrder; + } } } } diff --git a/src/umbraco.cms/businesslogic/web/DocumentType.cs b/src/umbraco.cms/businesslogic/web/DocumentType.cs index 31d5d89e14..154e374c04 100644 --- a/src/umbraco.cms/businesslogic/web/DocumentType.cs +++ b/src/umbraco.cms/businesslogic/web/DocumentType.cs @@ -547,6 +547,7 @@ namespace umbraco.cms.businesslogic.web var tabx = xd.CreateElement("Tab"); tabx.AppendChild(XmlHelper.AddTextNode(xd, "Id", propertyTypeGroup.Id.ToString())); tabx.AppendChild(XmlHelper.AddTextNode(xd, "Caption", propertyTypeGroup.Name)); + tabx.AppendChild(XmlHelper.AddTextNode(xd, "SortOrder", propertyTypeGroup.SortOrder.ToString())); tabs.AppendChild(tabx); } } From e17b5e6a7b04de4e88f731c58fe988f2f73359cf Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Thu, 12 Dec 2013 15:32:52 +0100 Subject: [PATCH 11/27] Update PublishedContentExtensions.cs Added overloaded extension methods for Next() and Previous() to take a delegate (eg. lambda expression) as a second parameter. The methods are based on this thread by Jeavon Leopold: http://our.umbraco.org/forum/developers/razor/46832-Filtered-Next()-and-Previous()-v7-UmbracoHelperMVC The code is tested in 7.0.0, but since it only relies on the existing Next() and Previous() methods, it should work in 6.1.x and 7.1.1 as well. --- src/Umbraco.Web/PublishedContentExtensions.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 51d1fda6ca..bd67a380fa 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -990,6 +990,16 @@ namespace Umbraco.Web { return content.Next(0); } + + public static IPublishedContent Next(this IPublishedContent current, Func func) { + IPublishedContent next = current.Next(); + while (next != null) { + if (func(next)) return next; + next = next.Next(); + } + return null; + } + public static IPublishedContent Next(this IPublishedContent content, int number) { var ownersList = content.GetOwnersList(); @@ -1022,6 +1032,16 @@ namespace Umbraco.Web { return content.Previous(0); } + + public static IPublishedContent Previous(this IPublishedContent current, Func func) { + IPublishedContent prev = current.Previous(); + while (prev != null) { + if (func(prev)) return prev; + prev = prev.Previous(); + } + return null; + } + public static IPublishedContent Previous(this IPublishedContent content, int number) { var ownersList = content.GetOwnersList(); @@ -1227,4 +1247,4 @@ namespace Umbraco.Web set { _getPropertyAliasesAndNames = value; } } } -} \ No newline at end of file +} From eeaa9166ac87ba46b7dbec38c0ce0989b1d7b9e5 Mon Sep 17 00:00:00 2001 From: james-melville Date: Fri, 13 Dec 2013 10:25:32 +0000 Subject: [PATCH 12/27] Fixes: U4-3814 - 6.1.6 XSS vulnerability in backoffice --- src/umbraco.controls/TabView.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/umbraco.controls/TabView.cs b/src/umbraco.controls/TabView.cs index dfd9b7f9a7..55923cee15 100644 --- a/src/umbraco.controls/TabView.cs +++ b/src/umbraco.controls/TabView.cs @@ -98,7 +98,7 @@ namespace umbraco.uicontrols { string TabId = this.ClientID + "_tab0" + (i + 1); writer.WriteLine("
  • "); writer.WriteLine(" "); - writer.WriteLine(" " + TabPageCaption + ""); + writer.WriteLine(" " + HttpUtility.HtmlEncode(TabPageCaption) + ""); writer.WriteLine(" "); writer.WriteLine("
  • "); } @@ -107,9 +107,9 @@ namespace umbraco.uicontrols { writer.WriteLine("
    "); this.RenderChildren(writer); writer.WriteLine("\t
    "); - writer.WriteLine("\t"); + writer.WriteLine("\t"); writer.WriteLine(""); - writer.WriteLine(""); + writer.WriteLine(""); } } } \ No newline at end of file From 1c901e00cc65e24b9023016cc30f267c1d414f25 Mon Sep 17 00:00:00 2001 From: esunxray Date: Thu, 19 Dec 2013 14:53:50 +0800 Subject: [PATCH 13/27] Update zh.xml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add line 重设密码 --- src/Umbraco.Web.UI/umbraco/config/lang/zh.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml index 472ac1252c..27cb212bc7 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml @@ -907,6 +907,7 @@ 更改密码 禁用后台管理界面 密码 + 重设密码 您的密码已更改! 重输密码 当前密码 From 2dd04799b216651314e780776e88e547290c7b8f Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Dec 2013 18:33:25 +1100 Subject: [PATCH 14/27] WIP membership provider updates --- .../Models/Membership/MembershipExtensions.cs | 6 +- .../Querying/ModelToSqlExpressionHelper.cs | 4 +- .../Querying/SqlStringExtensions.cs | 10 + .../Querying/StringPropertyMatchType.cs | 4 +- .../SqlSyntax/ISqlSyntaxProvider.cs | 1 + .../SqlSyntax/SqlCeSyntaxProvider.cs | 14 + .../SqlSyntax/SqlServerSyntaxProvider.cs | 14 + .../SqlSyntax/SqlSyntaxProviderBase.cs | 6 + .../Security/MembershipProviderBase.cs | 312 ++- src/Umbraco.Core/Services/MemberService.cs | 6 + .../Membership/MembershipProviderBaseTests.cs | 127 ++ .../Providers/MembersMembershipProvider.cs | 745 ++---- .../UsersMembershipProvider.cs | 10 +- ...ovider.cs => UmbracoMembershipProvider.cs} | 1989 ++++++++--------- ...eProvider.cs => UmbracoProfileProvider.cs} | 346 +-- ...RoleProvider.cs => UmbracoRoleProvider.cs} | 509 ++--- .../umbraco.providers.csproj | 6 +- 17 files changed, 2094 insertions(+), 2015 deletions(-) rename src/umbraco.providers/members/{MembersMembershipProvider.cs => UmbracoMembershipProvider.cs} (71%) rename src/umbraco.providers/members/{MembersProfileProvider.cs => UmbracoProfileProvider.cs} (69%) rename src/umbraco.providers/members/{MembersRoleProvider.cs => UmbracoRoleProvider.cs} (97%) diff --git a/src/Umbraco.Core/Models/Membership/MembershipExtensions.cs b/src/Umbraco.Core/Models/Membership/MembershipExtensions.cs index a986869371..118ba46669 100644 --- a/src/Umbraco.Core/Models/Membership/MembershipExtensions.cs +++ b/src/Umbraco.Core/Models/Membership/MembershipExtensions.cs @@ -5,15 +5,15 @@ namespace Umbraco.Core.Models.Membership { internal static class MembershipExtensions { - internal static MembershipUser AsConcreteMembershipUser(this IMember member) + internal static UmbracoMembershipMember AsConcreteMembershipUser(this IMember member) { var membershipMember = new UmbracoMembershipMember(member); return membershipMember; } - internal static IMember AsIMember(this MembershipUser membershipMember) + internal static IMember AsIMember(this UmbracoMembershipMember membershipMember) { - var member = membershipMember as UmbracoMembershipMember; + var member = membershipMember; if (member != null) { return member.Member; diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs index 611d9c63a3..4dba620ab0 100644 --- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs @@ -232,6 +232,8 @@ namespace Umbraco.Core.Persistence.Querying { switch (verb) { + case "SqlWildcard": + return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, EscapeAtArgument(RemoveQuote(val)), columnType); case "Equals": return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnEqualComparison(col, EscapeAtArgument(RemoveQuote(val)), columnType); case "StartsWith": @@ -280,7 +282,7 @@ namespace Umbraco.Core.Persistence.Querying return string.Format("upper({0})", r); case "ToLower": return string.Format("lower({0})", r); - + case "SqlWildcard": case "StartsWith": case "EndsWith": case "Contains": diff --git a/src/Umbraco.Core/Persistence/Querying/SqlStringExtensions.cs b/src/Umbraco.Core/Persistence/Querying/SqlStringExtensions.cs index 82c158a9c6..cbecc0a591 100644 --- a/src/Umbraco.Core/Persistence/Querying/SqlStringExtensions.cs +++ b/src/Umbraco.Core/Persistence/Querying/SqlStringExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Text.RegularExpressions; namespace Umbraco.Core.Persistence.Querying { @@ -7,6 +8,15 @@ namespace Umbraco.Core.Persistence.Querying /// internal static class SqlStringExtensions { + public static bool SqlWildcard(this string str, string txt, TextColumnType columnType) + { + var wildcardmatch = new Regex("^" + Regex.Escape(txt). + //deal with any wildcard chars % + Replace(@"\%", ".*") + "$"); + + return wildcardmatch.IsMatch(str); + } + public static bool SqlContains(this string str, string txt, TextColumnType columnType) { return str.InvariantContains(txt); diff --git a/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs b/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs index f4245b931a..f45efb412a 100644 --- a/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs +++ b/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs @@ -8,6 +8,8 @@ Exact, Contains, StartsWith, - EndsWith + EndsWith, + //Deals with % as wildcard chars in a string + Wildcard } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index 9fbf0456b6..2b31fb2032 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -15,6 +15,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType); string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType); string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType); + string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType); string GetQuotedTableName(string tableName); string GetQuotedColumnName(string columnName); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index d908f5c9b6..d9e9599dab 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -116,6 +116,20 @@ namespace Umbraco.Core.Persistence.SqlSyntax } } + public override string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnContainsComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for upper methods with NText columns + return string.Format("{0} LIKE '{1}'", column, value); + default: + throw new ArgumentOutOfRangeException("columnType"); + } + } + public override string GetQuotedTableName(string tableName) { return string.Format("[{0}]", tableName); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 24177c61d5..4046a7575d 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -90,6 +90,20 @@ namespace Umbraco.Core.Persistence.SqlSyntax } } + public override string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnContainsComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for upper methods with NText columns + return string.Format("{0} LIKE '{1}'", column, value); + default: + throw new ArgumentOutOfRangeException("columnType"); + } + } + public override string GetQuotedTableName(string tableName) { return string.Format("[{0}]", tableName); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index 8505d1e553..8f9a84437c 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -127,6 +127,12 @@ namespace Umbraco.Core.Persistence.SqlSyntax return string.Format("upper({0}) like '%{1}%'", column, value.ToUpper()); } + public virtual string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) like '{1}'", column, value.ToUpper()); + } + public virtual string GetQuotedTableName(string tableName) { return string.Format("\"{0}\"", tableName); diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs index 10a05d042e..24a0e29409 100644 --- a/src/Umbraco.Core/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs @@ -7,9 +7,110 @@ using System.Text.RegularExpressions; using System.Web.Configuration; using System.Web.Hosting; using System.Web.Security; +using Umbraco.Core.Logging; namespace Umbraco.Core.Security { + + public abstract class UmbracoMembershipProviderBase : MembershipProviderBase + { + protected UmbracoMembershipProviderBase() + { + //Set the defaults! + DefaultMemberTypeAlias = "Member"; + LockPropertyTypeAlias = Constants.Conventions.Member.IsLockedOut; + LastLockedOutPropertyTypeAlias = Constants.Conventions.Member.LastLockoutDate; + FailedPasswordAttemptsPropertyTypeAlias = Constants.Conventions.Member.FailedPasswordAttempts; + ApprovedPropertyTypeAlias = Constants.Conventions.Member.IsApproved; + CommentPropertyTypeAlias = Constants.Conventions.Member.Comments; + LastLoginPropertyTypeAlias = Constants.Conventions.Member.LastLoginDate; + LastPasswordChangedPropertyTypeAlias = Constants.Conventions.Member.LastPasswordChangeDate; + PasswordRetrievalQuestionPropertyTypeAlias = Constants.Conventions.Member.PasswordQuestion; + PasswordRetrievalAnswerPropertyTypeAlias = Constants.Conventions.Member.PasswordAnswer; + } + + public string DefaultMemberTypeAlias { get; protected set; } + public string LockPropertyTypeAlias { get; protected set; } + public string LastLockedOutPropertyTypeAlias { get; protected set; } + public string FailedPasswordAttemptsPropertyTypeAlias { get; protected set; } + public string ApprovedPropertyTypeAlias { get; protected set; } + public string CommentPropertyTypeAlias { get; protected set; } + public string LastLoginPropertyTypeAlias { get; protected set; } + public string LastPasswordChangedPropertyTypeAlias { get; protected set; } + public string PasswordRetrievalQuestionPropertyTypeAlias { get; protected set; } + public string PasswordRetrievalAnswerPropertyTypeAlias { get; protected set; } + + public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) + { + base.ChangePasswordQuestionAndAnswer(username, password, newPasswordQuestion, newPasswordAnswer); + + if (string.IsNullOrEmpty(PasswordRetrievalQuestionPropertyTypeAlias) || string.IsNullOrEmpty(PasswordRetrievalAnswerPropertyTypeAlias)) + { + throw new NotSupportedException("Updating the password Question and Answer is not valid if the properties aren't set in the config file"); + } + + return PerformChangePasswordQuestionAndAnswer(username, password, newPasswordQuestion, newPasswordAnswer); + } + + /// + /// Adds a new membership user to the data source. + /// + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + protected sealed override MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + { + return PerformCreateUser(DefaultMemberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + } + + /// + /// Adds a new membership user to the data source. + /// + /// The member type alias to use when creating the member + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + public MembershipUser CreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + { + //do the base validation first + base.CreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + + return PerformCreateUser(memberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + } + + /// + /// Adds a new membership user to the data source. + /// + /// The member type alias to use when creating the member + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + protected abstract MembershipUser PerformCreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status); + } /// /// A base membership provider class offering much of the underlying functionality for initializing and password encryption/hashing. /// @@ -18,7 +119,7 @@ namespace Umbraco.Core.Security /// /// Providers can override this setting, default is 7 /// - protected virtual int DefaultMinPasswordLength + public virtual int DefaultMinPasswordLength { get { return 7; } } @@ -26,7 +127,7 @@ namespace Umbraco.Core.Security /// /// Providers can override this setting, default is 1 /// - protected virtual int DefaultMinNonAlphanumericChars + public virtual int DefaultMinNonAlphanumericChars { get { return 1; } } @@ -34,7 +135,7 @@ namespace Umbraco.Core.Security /// /// Providers can override this setting, default is false to use better security /// - protected virtual bool DefaultUseLegacyEncoding + public virtual bool DefaultUseLegacyEncoding { get { return false; } } @@ -44,7 +145,7 @@ namespace Umbraco.Core.Security /// authenticate the username + password when ChangePassword is called. This property exists purely for /// backwards compatibility. /// - internal virtual bool AllowManuallyChangingPassword + public virtual bool AllowManuallyChangingPassword { get { return false; } } @@ -60,7 +161,8 @@ namespace Umbraco.Core.Security private string _passwordStrengthRegularExpression; private bool _requiresQuestionAndAnswer; private bool _requiresUniqueEmail; - private bool _useLegacyEncoding; + + internal bool UseLegacyEncoding; #region Properties @@ -208,7 +310,7 @@ namespace Umbraco.Core.Security _enablePasswordRetrieval = config.GetValue("enablePasswordRetrieval", false); _enablePasswordReset = config.GetValue("enablePasswordReset", false); _requiresQuestionAndAnswer = config.GetValue("requiresQuestionAndAnswer", false); - _requiresUniqueEmail = config.GetValue("requiresUniqueEmail", false); + _requiresUniqueEmail = config.GetValue("requiresUniqueEmail", true); _maxInvalidPasswordAttempts = GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0); _passwordAttemptWindow = GetIntValue(config, "passwordAttemptWindow", 10, false, 0); _minRequiredPasswordLength = GetIntValue(config, "minRequiredPasswordLength", DefaultMinPasswordLength, true, 0x80); @@ -220,10 +322,10 @@ namespace Umbraco.Core.Security _applicationName = GetDefaultAppName(); //by default we will continue using the legacy encoding. - _useLegacyEncoding = config.GetValue("useLegacyEncoding", DefaultUseLegacyEncoding); + UseLegacyEncoding = config.GetValue("useLegacyEncoding", DefaultUseLegacyEncoding); - // make sure password format is clear by default. - string str = config["passwordFormat"] ?? "Clear"; + // make sure password format is Hashed by default. + string str = config["passwordFormat"] ?? "Hashed"; switch (str.ToLower()) { @@ -244,7 +346,11 @@ namespace Umbraco.Core.Security } if ((PasswordFormat == MembershipPasswordFormat.Hashed) && EnablePasswordRetrieval) - throw new ProviderException("Provider can not retrieve hashed password"); + { + var ex = new ProviderException("Provider can not retrieve a hashed password"); + LogHelper.Error("Cannot specify a Hashed password format with the enabledPasswordRetrieval option set to true", ex); + throw ex; + } } @@ -271,14 +377,19 @@ namespace Umbraco.Core.Security AlphanumericChars, Strength } - + /// - /// Checks to ensure the AllowManuallyChangingPassword rule is adhered to + /// Processes a request to update the password for a membership user. /// - /// - /// - /// - /// + /// The user to update the password for. + /// This property is ignore for this provider + /// The new password for the specified user. + /// + /// true if the password was updated successfully; otherwise, false. + /// + /// + /// Checks to ensure the AllowManuallyChangingPassword rule is adhered to + /// public sealed override bool ChangePassword(string username, string oldPassword, string newPassword) { if (oldPassword.IsNullOrWhiteSpace() && AllowManuallyChangingPassword == false) @@ -290,8 +401,139 @@ namespace Umbraco.Core.Security return PerformChangePassword(username, oldPassword, newPassword); } + /// + /// Processes a request to update the password for a membership user. + /// + /// The user to update the password for. + /// This property is ignore for this provider + /// The new password for the specified user. + /// + /// true if the password was updated successfully; otherwise, false. + /// protected abstract bool PerformChangePassword(string username, string oldPassword, string newPassword); + /// + /// Processes a request to update the password question and answer for a membership user. + /// + /// The user to change the password question and answer for. + /// The password for the specified user. + /// The new password question for the specified user. + /// The new password answer for the specified user. + /// + /// true if the password question and answer are updated successfully; otherwise, false. + /// + /// + /// Performs the basic validation before passing off to PerformChangePasswordQuestionAndAnswer + /// + public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) + { + if (RequiresQuestionAndAnswer == false) + { + throw new NotSupportedException("Updating the password Question and Answer is not available if requiresQuestionAndAnswer is not set in web.config"); + } + + if (ValidateUser(username, password) == false) + { + return false; + } + + return PerformChangePasswordQuestionAndAnswer(username, password, newPasswordQuestion, newPasswordAnswer); + } + + /// + /// Processes a request to update the password question and answer for a membership user. + /// + /// The user to change the password question and answer for. + /// The password for the specified user. + /// The new password question for the specified user. + /// The new password answer for the specified user. + /// + /// true if the password question and answer are updated successfully; otherwise, false. + /// + protected abstract bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer); + + /// + /// Adds a new membership user to the data source. + /// + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + /// + /// Ensures the ValidatingPassword event is executed before executing PerformCreateUser and performs basic membership provider validation of values. + /// + public sealed override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + { + var args = new ValidatePasswordEventArgs(username, password, true); + OnValidatingPassword(args); + if (args.Cancel) + { + status = MembershipCreateStatus.InvalidPassword; + return null; + } + + // Validate password + var passwordValidAttempt = IsPasswordValid(password, MinRequiredNonAlphanumericCharacters, PasswordStrengthRegularExpression, MinRequiredPasswordLength); + if (passwordValidAttempt.Success == false) + { + status = MembershipCreateStatus.InvalidPassword; + return null; + } + + // Validate email + if (IsEmailValid(email) == false) + { + status = MembershipCreateStatus.InvalidEmail; + return null; + } + + // Make sure username isn't all whitespace + if (string.IsNullOrWhiteSpace(username.Trim())) + { + status = MembershipCreateStatus.InvalidUserName; + return null; + } + + // Check password question + if (string.IsNullOrWhiteSpace(passwordQuestion) && RequiresQuestionAndAnswer) + { + status = MembershipCreateStatus.InvalidQuestion; + return null; + } + + // Check password answer + if (string.IsNullOrWhiteSpace(passwordAnswer) && RequiresQuestionAndAnswer) + { + status = MembershipCreateStatus.InvalidAnswer; + return null; + } + + return PerformCreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + } + + /// + /// Adds a new membership user to the data source. + /// + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + protected abstract MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status); + protected internal static Attempt IsPasswordValid(string password, int minRequiredNonAlphanumericChars, string strengthRegex, int minLength) { if (minRequiredNonAlphanumericChars > 0) @@ -374,7 +616,7 @@ namespace Umbraco.Core.Security protected string FormatPasswordForStorage(string pass, string salt) { - if (_useLegacyEncoding) + if (UseLegacyEncoding) { return pass; } @@ -383,11 +625,20 @@ namespace Umbraco.Core.Security return salt + pass; } + protected bool IsEmailValid(string email) + { + const string pattern = @"^(?!\.)(""([^""\r\\]|\\[""\r\\])*""|" + + @"([-a-z0-9!#$%&'*+/=?^_`{|}~]|(? protected string EncryptOrHashExistingPassword(string storedPassword) { - if (_useLegacyEncoding) + if (UseLegacyEncoding) { return EncryptOrHashPassword(storedPassword, storedPassword); } @@ -481,7 +732,7 @@ namespace Umbraco.Core.Security { //if we are doing it the old way - if (_useLegacyEncoding) + if (UseLegacyEncoding) { return LegacyUnEncodePassword(pass); } @@ -533,7 +784,7 @@ namespace Umbraco.Core.Security protected HashAlgorithm GetHashAlgorithm(string password) { - if (_useLegacyEncoding) + if (UseLegacyEncoding) { //before we were never checking for an algorithm type so we were always using HMACSHA1 // for any SHA specified algorithm :( so we'll need to keep doing that for backwards compat support. @@ -601,5 +852,24 @@ namespace Umbraco.Core.Security return password; } + public override string ToString() + { + var result = base.ToString(); + var sb = new StringBuilder(result); + sb.AppendLine("Name =" + Name); + sb.AppendLine("_applicationName =" + _applicationName); + sb.AppendLine("_enablePasswordReset=" + _enablePasswordReset); + sb.AppendLine("_enablePasswordRetrieval=" + _enablePasswordRetrieval); + sb.AppendLine("_maxInvalidPasswordAttempts=" + _maxInvalidPasswordAttempts); + sb.AppendLine("_minRequiredNonAlphanumericCharacters=" + _minRequiredNonAlphanumericCharacters); + sb.AppendLine("_minRequiredPasswordLength=" + _minRequiredPasswordLength); + sb.AppendLine("_passwordAttemptWindow=" + _passwordAttemptWindow); + sb.AppendLine("_passwordFormat=" + _passwordFormat); + sb.AppendLine("_passwordStrengthRegularExpression=" + _passwordStrengthRegularExpression); + sb.AppendLine("_requiresQuestionAndAnswer=" + _requiresQuestionAndAnswer); + sb.AppendLine("_requiresUniqueEmail=" + _requiresUniqueEmail); + return sb.ToString(); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 0f4cd7c242..a84734d1f7 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -206,6 +206,9 @@ namespace Umbraco.Core.Services case StringPropertyMatchType.EndsWith: query.Where(member => member.Email.EndsWith(emailStringToMatch)); break; + case StringPropertyMatchType.Wildcard: + query.Where(member => member.Email.SqlWildcard(emailStringToMatch, TextColumnType.NVarchar)); + break; default: throw new ArgumentOutOfRangeException("matchType"); } @@ -235,6 +238,9 @@ namespace Umbraco.Core.Services case StringPropertyMatchType.EndsWith: query.Where(member => member.Username.EndsWith(login)); break; + case StringPropertyMatchType.Wildcard: + query.Where(member => member.Email.SqlWildcard(login, TextColumnType.NVarchar)); + break; default: throw new ArgumentOutOfRangeException("matchType"); } diff --git a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs index 500b33501c..06e44be215 100644 --- a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs +++ b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; +using System.Configuration.Provider; using System.Diagnostics; using System.Linq; using System.Text; @@ -14,6 +16,48 @@ namespace Umbraco.Tests.Membership [TestFixture] public class MembershipProviderBaseTests { + //[Test] + //public void Change_Password_Base_Validation() + + //[Test] + //public void ChangePasswordQuestionAndAnswer_Base_Validation() + + //[Test] + //public void CreateUser_Base_Validation() + + [Test] + public void Sets_Defaults() + { + var provider = new TestProvider(); + provider.Initialize("test", new NameValueCollection()); + + Assert.AreEqual("test", provider.Name); + Assert.AreEqual(MembershipProviderBase.GetDefaultAppName(), provider.ApplicationName); + Assert.AreEqual(false, provider.EnablePasswordRetrieval); + Assert.AreEqual(false, provider.EnablePasswordReset); + Assert.AreEqual(false, provider.RequiresQuestionAndAnswer); + Assert.AreEqual(true, provider.RequiresUniqueEmail); + Assert.AreEqual(5, provider.MaxInvalidPasswordAttempts); + Assert.AreEqual(10, provider.PasswordAttemptWindow); + Assert.AreEqual(provider.DefaultMinPasswordLength, provider.MinRequiredPasswordLength); + Assert.AreEqual(provider.DefaultMinNonAlphanumericChars, provider.MinRequiredNonAlphanumericCharacters); + Assert.AreEqual(null, provider.PasswordStrengthRegularExpression); + Assert.AreEqual(provider.DefaultUseLegacyEncoding, provider.UseLegacyEncoding); + Assert.AreEqual(MembershipPasswordFormat.Hashed, provider.PasswordFormat); + } + + [Test] + public void Throws_Exception_With_Hashed_Password_And_Password_Retrieval() + { + var provider = new TestProvider(); + + Assert.Throws(() => provider.Initialize("test", new NameValueCollection() + { + {"enablePasswordRetrieval", "true"}, + {"passwordFormat", "Hashed"} + })); + } + [TestCase("hello", 0, "", 5, true)] [TestCase("hello", 0, "", 4, true)] [TestCase("hello", 0, "", 6, false)] @@ -65,5 +109,88 @@ namespace Umbraco.Tests.Membership Assert.AreEqual(salt, initSalt); } + private class TestProvider : MembershipProviderBase + { + public override string GetPassword(string username, string answer) + { + throw new NotImplementedException(); + } + + public override string ResetPassword(string username, string answer) + { + throw new NotImplementedException(); + } + + public override void UpdateUser(MembershipUser user) + { + throw new NotImplementedException(); + } + + public override bool ValidateUser(string username, string password) + { + throw new NotImplementedException(); + } + + public override bool UnlockUser(string userName) + { + throw new NotImplementedException(); + } + + public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) + { + throw new NotImplementedException(); + } + + public override MembershipUser GetUser(string username, bool userIsOnline) + { + throw new NotImplementedException(); + } + + public override string GetUserNameByEmail(string email) + { + throw new NotImplementedException(); + } + + public override bool DeleteUser(string username, bool deleteAllRelatedData) + { + throw new NotImplementedException(); + } + + public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) + { + throw new NotImplementedException(); + } + + public override int GetNumberOfUsersOnline() + { + throw new NotImplementedException(); + } + + public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) + { + throw new NotImplementedException(); + } + + public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) + { + throw new NotImplementedException(); + } + + protected override bool PerformChangePassword(string username, string oldPassword, string newPassword) + { + throw new NotImplementedException(); + } + + protected override bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) + { + throw new NotImplementedException(); + } + + protected override MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + { + throw new NotImplementedException(); + } + } + } } diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index f423d95fbd..23ee46fabc 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -11,6 +11,8 @@ using System.Web.Security; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Models.Membership; @@ -19,25 +21,8 @@ namespace Umbraco.Web.Security.Providers /// /// Custom Membership Provider for Umbraco Members (User authentication for Frontend applications NOT umbraco CMS) /// - internal class MembersMembershipProvider : MembershipProvider + internal class MembersMembershipProvider : UmbracoMembershipProviderBase { - #region Fields - private string _applicationName; - private bool _enablePasswordReset; - private bool _enablePasswordRetrieval; - private int _maxInvalidPasswordAttempts; - private int _minRequiredNonAlphanumericCharacters; - private int _minRequiredPasswordLength; - private int _passwordAttemptWindow; - - private MembershipPasswordFormat _passwordFormat; - - private string _passwordStrengthRegularExpression; - private bool _requiresQuestionAndAnswer; - private bool _requiresUniqueEmail; - - #endregion - private IMembershipMemberService _memberService; protected IMembershipMemberService MemberService @@ -45,125 +30,8 @@ namespace Umbraco.Web.Security.Providers get { return _memberService ?? (_memberService = ApplicationContext.Current.Services.MemberService); } } - /// - /// The name of the application using the custom membership provider. - /// - /// - /// The name of the application using the custom membership provider. - public override string ApplicationName + public string ProviderName { - get { return _applicationName; } - set { _applicationName = value; } - } - - /// - /// Indicates whether the membership provider is configured to allow users to reset their passwords. - /// - /// - /// true if the membership provider supports password reset; otherwise, false. The default is true. - public override bool EnablePasswordReset - { - get { return _enablePasswordReset; } - } - - /// - /// Indicates whether the membership provider is configured to allow users to retrieve their passwords. - /// - /// - /// true if the membership provider is configured to support password retrieval; otherwise, false. The default is false. - public override bool EnablePasswordRetrieval - { - get { return _enablePasswordRetrieval; } - } - - /// - /// Gets the number of invalid password or password-answer attempts allowed before the membership user is locked out. - /// - /// - /// The number of invalid password or password-answer attempts allowed before the membership user is locked out. - public override int MaxInvalidPasswordAttempts - { - get { return _maxInvalidPasswordAttempts; } - } - - /// - /// Gets the minimum number of special characters that must be present in a valid password. - /// - /// - /// The minimum number of special characters that must be present in a valid password. - public override int MinRequiredNonAlphanumericCharacters - { - get { return _minRequiredNonAlphanumericCharacters; } - } - - /// - /// Gets the minimum length required for a password. - /// - /// - /// The minimum length required for a password. - public override int MinRequiredPasswordLength - { - get { return _minRequiredPasswordLength; } - } - - /// - /// Gets the number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out. - /// - /// - /// The number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out. - public override int PasswordAttemptWindow - { - get { return _passwordAttemptWindow; } - } - - /// - /// Gets a value indicating the format for storing passwords in the membership data store. - /// - /// - /// One of the values indicating the format for storing passwords in the data store. - public override MembershipPasswordFormat PasswordFormat - { - get { return _passwordFormat; } - } - - /// - /// Gets the regular expression used to evaluate a password. - /// - /// - /// A regular expression used to evaluate a password. - public override string PasswordStrengthRegularExpression - { - get { return _passwordStrengthRegularExpression; } - } - - /// - /// Gets a value indicating whether the membership provider is configured to require the user to answer a password question for password reset and retrieval. - /// - /// - /// true if a password answer is required for password reset and retrieval; otherwise, false. The default is true. - public override bool RequiresQuestionAndAnswer - { - get { return _requiresQuestionAndAnswer; } - } - - /// - /// Gets a value indicating whether the membership provider is configured to require a unique e-mail address for each user name. - /// - /// - /// true if the membership provider requires a unique e-mail address; otherwise, false. The default is true. - public override bool RequiresUniqueEmail - { - get { return _requiresUniqueEmail; } - } - - /// - /// The default Umbraco member type alias to create when registration is performed using .net Membership - /// - /// - /// Member type alias - public string DefaultMemberTypeAlias { get; private set; } - - public string ProviderName { get { return "MembersMembershipProvider"; } } @@ -186,80 +54,127 @@ namespace Umbraco.Web.Security.Providers // Initialize base provider class base.Initialize(name, config); - _applicationName = string.IsNullOrEmpty(config["applicationName"]) ? GetDefaultAppName() : config["applicationName"]; + //// test for membertype (if not specified, choose the first member type available) + //if (config["defaultMemberTypeAlias"] != null) + // _defaultMemberTypeAlias = config["defaultMemberTypeAlias"]; + //else if (MemberType.GetAll.Length == 1) + // _defaultMemberTypeAlias = MemberType.GetAll[0].Alias; + //else + // throw new ProviderException("No default MemberType alias is specified in the web.config string. Please add a 'defaultMemberTypeAlias' to the add element in the provider declaration in web.config"); - _enablePasswordRetrieval = GetBooleanValue(config, "enablePasswordRetrieval", false); - _enablePasswordReset = GetBooleanValue(config, "enablePasswordReset", false); - _requiresQuestionAndAnswer = GetBooleanValue(config, "requiresQuestionAndAnswer", false); - _requiresUniqueEmail = GetBooleanValue(config, "requiresUniqueEmail", true); - _maxInvalidPasswordAttempts = GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0); - _passwordAttemptWindow = GetIntValue(config, "passwordAttemptWindow", 10, false, 0); - _minRequiredPasswordLength = GetIntValue(config, "minRequiredPasswordLength", 7, true, 0x80); - _minRequiredNonAlphanumericCharacters = GetIntValue(config, "minRequiredNonalphanumericCharacters", 1, true, 0x80); - _passwordStrengthRegularExpression = config["passwordStrengthRegularExpression"]; - - // make sure password format is Hashed by default. - var str = config["passwordFormat"] ?? "Hashed"; - - LogHelper.Debug("Loaded membership provider properties"); - LogHelper.Debug(ToString()); - - switch (str.ToLower()) + // test for approve status + if (config["umbracoApprovePropertyTypeAlias"] != null) { - case "clear": - _passwordFormat = MembershipPasswordFormat.Clear; - break; - case "encrypted": - _passwordFormat = MembershipPasswordFormat.Encrypted; - break; - case "hashed": - _passwordFormat = MembershipPasswordFormat.Hashed; - break; - default: - var e = new ProviderException("Provider bad password format"); - LogHelper.Error(e.Message, e); - throw e; + ApprovedPropertyTypeAlias = config["umbracoApprovePropertyTypeAlias"]; } - - if ((PasswordFormat == MembershipPasswordFormat.Hashed) && EnablePasswordRetrieval) + // test for lock attempts + if (config["umbracoLockPropertyTypeAlias"] != null) { - var e = new ProviderException("Provider can not retrieve hashed password"); - LogHelper.Error(e.Message, e); - throw e; + LockPropertyTypeAlias = config["umbracoLockPropertyTypeAlias"]; } - - // TODO: rationalise what happens when no member alias is specified.... - DefaultMemberTypeAlias = config["defaultMemberTypeAlias"]; - - LogHelper.Debug("Finished initialising member ship provider " + GetType().FullName); + if (config["umbracoLastLockedPropertyTypeAlias"] != null) + { + LastLockedOutPropertyTypeAlias = config["umbracoLastLockedPropertyTypeAlias"]; + } + if (config["umbracoLastPasswordChangedPropertyTypeAlias"] != null) + { + LastPasswordChangedPropertyTypeAlias = config["umbracoLastPasswordChangedPropertyTypeAlias"]; + } + if (config["umbracoFailedPasswordAttemptsPropertyTypeAlias"] != null) + { + FailedPasswordAttemptsPropertyTypeAlias = config["umbracoFailedPasswordAttemptsPropertyTypeAlias"]; + } + // comment property + if (config["umbracoCommentPropertyTypeAlias"] != null) + { + CommentPropertyTypeAlias = config["umbracoCommentPropertyTypeAlias"]; + } + // last login date + if (config["umbracoLastLoginPropertyTypeAlias"] != null) + { + LastLoginPropertyTypeAlias = config["umbracoLastLoginPropertyTypeAlias"]; + } + // password retrieval + if (config["umbracoPasswordRetrievalQuestionPropertyTypeAlias"] != null) + { + PasswordRetrievalQuestionPropertyTypeAlias = config["umbracoPasswordRetrievalQuestionPropertyTypeAlias"]; + } + if (config["umbracoPasswordRetrievalAnswerPropertyTypeAlias"] != null) + { + PasswordRetrievalAnswerPropertyTypeAlias = config["umbracoPasswordRetrievalAnswerPropertyTypeAlias"]; + } } - public override string ToString() + /// + /// Processes a request to update the password for a membership user. + /// + /// The user to update the password for. + /// This property is ignore for this provider + /// The new password for the specified user. + /// + /// true if the password was updated successfully; otherwise, false. + /// + protected override bool PerformChangePassword(string username, string oldPassword, string newPassword) { - var result = base.ToString(); + //NOTE: due to backwards compatibilty reasons (and UX reasons), this provider doesn't care about the old password and + // allows simply setting the password manually so we don't really care about the old password. + // This is allowed based on the overridden AllowManuallyChangingPassword option. - result += "_applicationName =" + _applicationName + Environment.NewLine; - result += "_enablePasswordReset=" + _enablePasswordReset + Environment.NewLine; - result += "_enablePasswordRetrieval=" + _enablePasswordRetrieval + Environment.NewLine; - result += "_maxInvalidPasswordAttempts=" + _maxInvalidPasswordAttempts + Environment.NewLine; - result += "_minRequiredNonAlphanumericCharacters=" + _minRequiredNonAlphanumericCharacters + Environment.NewLine; - result += "_minRequiredPasswordLength=" + _minRequiredPasswordLength + Environment.NewLine; - result += "_passwordAttemptWindow=" + _passwordAttemptWindow + Environment.NewLine; + // in order to support updating passwords from the umbraco core, we can't validate the old password + var m = _memberService.GetByUsername(username); + if (m == null) return false; - result += "_passwordFormat=" + _passwordFormat + Environment.NewLine; + var args = new ValidatePasswordEventArgs(username, newPassword, false); + OnValidatingPassword(args); - result += "_passwordStrengthRegularExpression=" + _passwordStrengthRegularExpression + Environment.NewLine; - result += "_requiresQuestionAndAnswer=" + _requiresQuestionAndAnswer + Environment.NewLine; - result += "_requiresUniqueEmail=" + _requiresUniqueEmail + Environment.NewLine; - result += "DefaultMemberTypeAlias=" + DefaultMemberTypeAlias + Environment.NewLine; + if (args.Cancel) + { + if (args.FailureInformation != null) + throw args.FailureInformation; + throw new MembershipPasswordException("Change password canceled due to password validation failure."); + } - return result; + string salt; + var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); + + m.Password = FormatPasswordForStorage(encodedPassword, salt); + m.LastPasswordChangeDate = DateTime.Now; + + _memberService.Save(m); + + return true; + } + + /// + /// Processes a request to update the password question and answer for a membership user. + /// + /// The user to change the password question and answer for. + /// The password for the specified user. + /// The new password question for the specified user. + /// The new password answer for the specified user. + /// + /// true if the password question and answer are updated successfully; otherwise, false. + /// + protected override bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) + { + var member = MemberService.GetByUsername(username); + if (member == null) + { + return false; + } + + member.PasswordQuestion = newPasswordQuestion; + member.PasswordAnswer = newPasswordAnswer; + + MemberService.Save(member); + + return true; } /// /// Adds a new membership user to the data source with the specified member type /// - /// A specific member type to create the member for + /// A specific member type to create the member for /// The user name for the new user. /// The password for the new user. /// The e-mail address for the new user. @@ -271,48 +186,11 @@ namespace Umbraco.Web.Security.Providers /// /// A object populated with the information for the newly created user. /// - public MembershipUser CreateUser(string memberType, string username, string password, string email, string passwordQuestion, string passwordAnswer, - bool isApproved, object providerUserKey, out MembershipCreateStatus status) + protected override MembershipUser PerformCreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, + string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { - LogHelper.Debug("Member signup requested: username -> " + username + ". email -> " + email); - - // Validate password - if (IsPasswordValid(password) == false) - { - status = MembershipCreateStatus.InvalidPassword; - return null; - } - - // Validate email - if (IsEmaiValid(email) == false) - { - status = MembershipCreateStatus.InvalidEmail; - return null; - } - - // Make sure username isn't all whitespace - if (string.IsNullOrWhiteSpace(username.Trim())) - { - status = MembershipCreateStatus.InvalidUserName; - return null; - } - - // Check password question - if (string.IsNullOrWhiteSpace(passwordQuestion) && _requiresQuestionAndAnswer) - { - status = MembershipCreateStatus.InvalidQuestion; - return null; - } - - // Check password answer - if (string.IsNullOrWhiteSpace(passwordAnswer) && _requiresQuestionAndAnswer) - { - status = MembershipCreateStatus.InvalidAnswer; - return null; - } - // See if the user already exists - if (MemberService.GetByUsername(username) != null) + if (MemberService.Exists(username)) { status = MembershipCreateStatus.DuplicateUserName; LogHelper.Warn("Cannot create member as username already exists: " + username); @@ -328,14 +206,16 @@ namespace Umbraco.Web.Security.Providers return null; } - var member = MemberService.CreateMember(email, username, password, memberType); + string salt; + var encodedPassword = EncryptOrHashNewPassword(password, out salt); - member.IsApproved = isApproved; + var member = MemberService.CreateMember(email, username, encodedPassword, memberTypeAlias); + member.PasswordQuestion = passwordQuestion; member.PasswordAnswer = passwordAnswer; - - //encrypts/hashes the password depending on the settings - member.Password = EncryptOrHashPassword(member.Password); + member.IsApproved = isApproved; + member.LastLoginDate = DateTime.Now; + member.LastPasswordChangeDate = DateTime.Now; MemberService.Save(member); @@ -344,68 +224,72 @@ namespace Umbraco.Web.Security.Providers } /// - /// Adds a new membership user to the data source. + /// Removes a user from the membership data source. /// - /// The user name for the new user. - /// The password for the new user. - /// The e-mail address for the new user. - /// The password question for the new user. - /// The password answer for the new user - /// Whether or not the new user is approved to be validated. - /// The unique identifier from the membership data source for the user. - /// A enumeration value indicating whether the user was created successfully. + /// The name of the user to delete. + /// + /// TODO: This setting currently has no effect + /// /// - /// A object populated with the information for the newly created user. + /// true if the user was successfully deleted; otherwise, false. /// - public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, - bool isApproved, object providerUserKey, out MembershipCreateStatus status) + public override bool DeleteUser(string username, bool deleteAllRelatedData) { - return CreateUser(DefaultMemberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + var member = MemberService.GetByUsername(username); + if (member == null) return false; + + MemberService.Delete(member); + return true; } /// - /// Processes a request to update the password question and answer for a membership user. + /// Gets a collection of membership users where the e-mail address contains the specified e-mail address to match. /// - /// The user to change the password question and answer for. - /// The password for the specified user. - /// The new password question for the specified user. - /// The new password answer for the specified user. + /// The e-mail address to search for. + /// The index of the page of results to return. pageIndex is zero-based. + /// The size of the page of results to return. + /// The total number of matched users. /// - /// true if the password question and answer are updated successfully; otherwise, false. + /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. /// - public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, - string newPasswordAnswer) + public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { - if (_requiresQuestionAndAnswer == false) + var byEmail = MemberService.FindMembersByEmail(emailToMatch, StringPropertyMatchType.Wildcard).ToArray(); + totalRecords = byEmail.Length; + var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); + + var collection = new MembershipUserCollection(); + foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) { - throw new NotSupportedException("Updating the password Question and Answer is not available if requiresQuestionAndAnswer is not set in web.config"); + collection.Add(m.AsConcreteMembershipUser()); } - - if (ValidateUser(username, password) == false) - { - throw new MembershipPasswordException("Invalid username and password combinatio"); - } - - var member = MemberService.GetByUsername(username); - var encodedPassword = EncryptOrHashPassword(password); - - if (member.Password == encodedPassword) - { - member.PasswordQuestion = newPasswordQuestion; - member.PasswordAnswer = newPasswordAnswer; - - MemberService.Save(member); - - return true; - } - else - { - //TODO: Throw here? or just return false; - } - - return false; + return collection; } + /// + /// Gets a collection of membership users where the user name contains the specified user name to match. + /// + /// The user name to search for. + /// The index of the page of results to return. pageIndex is zero-based. + /// The size of the page of results to return. + /// The total number of matched users. + /// + /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. + /// + public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) + { + var byEmail = MemberService.FindMembersByUsername(usernameToMatch, StringPropertyMatchType.Wildcard).ToArray(); + totalRecords = byEmail.Length; + var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); + + var collection = new MembershipUserCollection(); + foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + { + collection.Add(m.AsConcreteMembershipUser()); + } + return collection; + } + /// /// Gets the password for the specified user name from the data source. /// @@ -416,59 +300,22 @@ namespace Umbraco.Web.Security.Providers /// public override string GetPassword(string username, string answer) { - if (_enablePasswordRetrieval == false) + if (EnablePasswordRetrieval == false) throw new ProviderException("Password Retrieval Not Enabled."); - if (_passwordFormat == MembershipPasswordFormat.Hashed) + if (PasswordFormat == MembershipPasswordFormat.Hashed) throw new ProviderException("Cannot retrieve Hashed passwords."); var member = MemberService.GetByUsername(username); - if (_requiresQuestionAndAnswer && member.PasswordAnswer != answer) + if (RequiresQuestionAndAnswer && member.PasswordAnswer != answer) { throw new ProviderException("Password retrieval answer doesn't match"); } return member.Password; } - - /// - /// Processes a request to update the password for a membership user. - /// - /// The user to update the password for. - /// The current password for the specified user. - /// The new password for the specified user. - /// - /// true if the password was updated successfully; otherwise, false. - /// - public override bool ChangePassword(string username, string oldPassword, string newPassword) - { - // Validate new password - if (IsPasswordValid(newPassword) == false) - { - var e = new MembershipPasswordException("Change password canceled due to new password validation failure."); - LogHelper.WarnWithException(e.Message, e); - throw e; - } - - var member = MemberService.GetByUsername(username); - if (member == null) return false; - - var encodedPassword = EncryptOrHashPassword(oldPassword); - - if (member.Password == encodedPassword) - { - - member.Password = EncryptOrHashPassword(newPassword); - MemberService.Save(member); - - return true; - } - - LogHelper.Warn("Can't change password as old password was incorrect"); - return false; - } - + /// /// Resets a user's password to a new, automatically generated password. /// @@ -477,30 +324,34 @@ namespace Umbraco.Web.Security.Providers /// The new password for the specified user. public override string ResetPassword(string username, string answer) { - if (_enablePasswordReset == false) - throw new ProviderException("Password reset is Not Enabled."); + //TODO: Get logic from other provider - var member = MemberService.GetByUsername(username); + throw new NotImplementedException(); - if (member == null) - throw new ProviderException("The supplied user is not found"); + //if (EnablePasswordReset == false) + // throw new ProviderException("Password reset is Not Enabled."); - if(member.IsLockedOut) - throw new ProviderException("The member is locked out."); + //var member = MemberService.GetByUsername(username); - if (_requiresQuestionAndAnswer == false || (_requiresQuestionAndAnswer && answer == member.PasswordAnswer)) - { - member.Password = - EncryptOrHashPassword(Membership.GeneratePassword(_minRequiredPasswordLength, - _minRequiredNonAlphanumericCharacters)); - MemberService.Save(member); - } - else - { - throw new MembershipPasswordException("Incorrect password answer"); - } + //if (member == null) + // throw new ProviderException("The supplied user is not found"); + + //if(member.IsLockedOut) + // throw new ProviderException("The member is locked out."); + + //if (RequiresQuestionAndAnswer == false || (RequiresQuestionAndAnswer && answer == member.PasswordAnswer)) + //{ + // member.Password = + // EncryptOrHashPassword(Membership.GeneratePassword(MinRequiredPasswordLength, + // MinRequiredNonAlphanumericCharacters)); + // MemberService.Save(member); + //} + //else + //{ + // throw new MembershipPasswordException("Incorrect password answer"); + //} - return null; + //return null; } /// @@ -511,8 +362,8 @@ namespace Umbraco.Web.Security.Providers /// A object that represents the user to update and the updated information for the user. public override void UpdateUser(MembershipUser user) { - var member = user.AsIMember(); - MemberService.Save(member); + //var member = user.AsIMember(); + //MemberService.Save(member); } /// @@ -532,7 +383,8 @@ namespace Umbraco.Web.Security.Providers if (member.IsLockedOut) throw new ProviderException("The member is locked out."); - var encodedPassword = EncryptOrHashPassword(password); + string salt; + var encodedPassword = EncryptOrHashNewPassword(password, out salt); var authenticated = (encodedPassword == member.Password); @@ -645,22 +497,7 @@ namespace Umbraco.Web.Security.Providers return member == null ? null : member.Username; } - /// - /// Removes a user from the membership data source. - /// - /// The name of the user to delete. - /// true to delete data related to the user from the database; false to leave data related to the user in the database. - /// - /// true if the user was successfully deleted; otherwise, false. - /// - public override bool DeleteUser(string username, bool deleteAllRelatedData) - { - var member = MemberService.GetByUsername(username); - if (member == null) return false; - - MemberService.Delete(member); - return true; - } + /// /// Gets a collection of all the users in the data source in pages of data. @@ -687,182 +524,14 @@ namespace Umbraco.Web.Security.Providers throw new System.NotImplementedException(); } - /// - /// Gets a collection of membership users where the user name contains the specified user name to match. - /// - /// The user name to search for. - /// The index of the page of results to return. pageIndex is zero-based. - /// The size of the page of results to return. - /// The total number of matched users. - /// - /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. - /// - public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) + + public override string ToString() { - throw new System.NotImplementedException(); + var result = base.ToString(); + var sb = new StringBuilder(result); + sb.AppendLine("DefaultMemberTypeAlias=" + DefaultMemberTypeAlias); + return sb.ToString(); } - /// - /// Gets a collection of membership users where the e-mail address contains the specified e-mail address to match. - /// - /// The e-mail address to search for. - /// The index of the page of results to return. pageIndex is zero-based. - /// The size of the page of results to return. - /// The total number of matched users. - /// - /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. - /// - public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) - { - var byEmail = MemberService.FindMembersByEmail(emailToMatch).ToArray(); - totalRecords = byEmail.Length; - var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); - - var collection = new MembershipUserCollection(); - foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) - { - collection.Add(m.AsConcreteMembershipUser()); - } - return collection; - } - - #region Private methods - - private bool IsPasswordValid(string password) - { - if (_minRequiredNonAlphanumericCharacters > 0) - { - var nonAlphaNumeric = Regex.Replace(password, "[a-zA-Z0-9]", "", RegexOptions.Multiline | RegexOptions.IgnoreCase); - if (nonAlphaNumeric.Length < _minRequiredNonAlphanumericCharacters) - { - return false; - } - } - - var valid = true; - if(string.IsNullOrEmpty(_passwordStrengthRegularExpression) == false) - { - valid = Regex.IsMatch(password, _passwordStrengthRegularExpression, RegexOptions.Compiled); - } - - return valid && password.Length >= _minRequiredPasswordLength; - } - - private bool IsEmaiValid(string email) - { - const string pattern = @"^(?!\.)(""([^""\r\\]|\\[""\r\\])*""|" - + @"([-a-z0-9!#$%&'*+/=?^_`{|}~]|(? - /// Encodes the password. - /// - /// The password. - /// The encoded password. - private string EncryptOrHashPassword(string password) - { - var encodedPassword = password; - switch (PasswordFormat) - { - case MembershipPasswordFormat.Clear: - break; - case MembershipPasswordFormat.Encrypted: - encodedPassword = - Convert.ToBase64String(EncryptPassword(Encoding.Unicode.GetBytes(password))); - break; - case MembershipPasswordFormat.Hashed: - var hash = new HMACSHA1 {Key = Encoding.Unicode.GetBytes(password)}; - encodedPassword = - Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password))); - break; - default: - throw new ProviderException("Unsupported password format."); - } - return encodedPassword; - } - - /// - /// Gets the boolean value. - /// - /// The config. - /// Name of the value. - /// if set to true [default value]. - /// - private bool GetBooleanValue(NameValueCollection config, string valueName, bool defaultValue) - { - bool flag; - var str = config[valueName]; - if (str == null) - return defaultValue; - - if (bool.TryParse(str, out flag) == false) - { - throw new ProviderException("Value must be boolean."); - } - return flag; - } - - /// - /// Gets the int value. - /// - /// The config. - /// Name of the value. - /// The default value. - /// if set to true [zero allowed]. - /// The max value allowed. - /// - private int GetIntValue(NameValueCollection config, string valueName, int defaultValue, bool zeroAllowed, int maxValueAllowed) - { - int num; - var s = config[valueName]; - if (s == null) - { - return defaultValue; - } - if (int.TryParse(s, out num) == false) - { - if (zeroAllowed) - { - throw new ProviderException("Value must be non negative integer"); - } - throw new ProviderException("Value must be positive integer"); - } - if (zeroAllowed && (num < 0)) - { - throw new ProviderException("Value must be non negativeinteger"); - } - if (zeroAllowed == false && (num <= 0)) - { - throw new ProviderException("Value must be positive integer"); - } - if ((maxValueAllowed > 0) && (num > maxValueAllowed)) - { - throw new ProviderException("Value too big"); - } - return num; - } - - - /// - /// Gets the name of the default app. - /// - /// - private string GetDefaultAppName() - { - try - { - var applicationVirtualPath = HostingEnvironment.ApplicationVirtualPath; - return string.IsNullOrEmpty(applicationVirtualPath) ? "/" : applicationVirtualPath; - } - catch - { - return "/"; - } - } - - #endregion } } \ No newline at end of file diff --git a/src/umbraco.providers/UsersMembershipProvider.cs b/src/umbraco.providers/UsersMembershipProvider.cs index 278aff9871..e862bf5b19 100644 --- a/src/umbraco.providers/UsersMembershipProvider.cs +++ b/src/umbraco.providers/UsersMembershipProvider.cs @@ -22,7 +22,7 @@ namespace umbraco.providers /// /// Override to maintain backwards compatibility with 0 required non-alphanumeric chars /// - protected override int DefaultMinNonAlphanumericChars + public override int DefaultMinNonAlphanumericChars { get { return 0; } } @@ -30,7 +30,7 @@ namespace umbraco.providers /// /// Override to maintain backwards compatibility with only 4 required length /// - protected override int DefaultMinPasswordLength + public override int DefaultMinPasswordLength { get { return 4; } } @@ -38,7 +38,7 @@ namespace umbraco.providers /// /// Override to maintain backwards compatibility /// - protected override bool DefaultUseLegacyEncoding + public override bool DefaultUseLegacyEncoding { get { return true; } } @@ -110,7 +110,7 @@ namespace umbraco.providers /// /// true if the password question and answer are updated successfully; otherwise, false. /// - public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) + protected override bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) { throw new Exception("The method or operation is not implemented."); } @@ -129,7 +129,7 @@ namespace umbraco.providers /// /// A object populated with the information for the newly created user. /// - public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + protected override MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { var args = new ValidatePasswordEventArgs(username, password, true); OnValidatingPassword(args); diff --git a/src/umbraco.providers/members/MembersMembershipProvider.cs b/src/umbraco.providers/members/UmbracoMembershipProvider.cs similarity index 71% rename from src/umbraco.providers/members/MembersMembershipProvider.cs rename to src/umbraco.providers/members/UmbracoMembershipProvider.cs index a14b47d84d..62562a85b0 100644 --- a/src/umbraco.providers/members/MembersMembershipProvider.cs +++ b/src/umbraco.providers/members/UmbracoMembershipProvider.cs @@ -1,1031 +1,958 @@ -#region namespace -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web.Security; -using System.Configuration; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Security; -using umbraco.BusinessLogic; -using System.Security.Cryptography; -using System.Web.Util; -using System.Collections.Specialized; -using System.Configuration.Provider; -using System.Security; -using System.Security.Permissions; -using System.Runtime.CompilerServices; -using Member = umbraco.cms.businesslogic.member.Member; -using MemberType = umbraco.cms.businesslogic.member.MemberType; - -#endregion - -namespace umbraco.providers.members -{ - /// - /// Custom Membership Provider for Umbraco Members (User authentication for Frontend applications NOT umbraco CMS) - /// - - public class UmbracoMembershipProvider : MembershipProviderBase - { - #region Fields - - //Set the defaults! - private string _defaultMemberTypeAlias = "Member"; - private string _lockPropertyTypeAlias = Constants.Conventions.Member.IsLockedOut; - private string _lastLockedOutPropertyTypeAlias = Constants.Conventions.Member.LastLockoutDate; - private string _failedPasswordAttemptsPropertyTypeAlias = Constants.Conventions.Member.FailedPasswordAttempts; - private string _approvedPropertyTypeAlias = Constants.Conventions.Member.IsApproved; - private string _commentPropertyTypeAlias = Constants.Conventions.Member.Comments; - private string _lastLoginPropertyTypeAlias = Constants.Conventions.Member.LastLoginDate; - private string _lastPasswordChangedPropertyTypeAlias = Constants.Conventions.Member.LastPasswordChangeDate; - private string _passwordRetrievalQuestionPropertyTypeAlias = Constants.Conventions.Member.PasswordQuestion; - private string _passwordRetrievalAnswerPropertyTypeAlias = Constants.Conventions.Member.PasswordAnswer; - - private string _providerName = Member.UmbracoMemberProviderName; - - //Need to expose these publicly so we know what field aliases to use in the editor, we only care about these 3 fields - // because they are the only 'settable' provider properties that are not stored against the IMember directly (i.e. they are - // property type properties). - - public string LockPropertyTypeAlias - { - get { return _lockPropertyTypeAlias; } - } - - public string ApprovedPropertyTypeAlias - { - get { return _approvedPropertyTypeAlias; } - } - - public string CommentPropertyTypeAlias - { - get { return _commentPropertyTypeAlias; } - } - - #endregion - - /// - /// Override to maintain backwards compatibility with 0 required non-alphanumeric chars - /// - protected override int DefaultMinNonAlphanumericChars - { - get { return 0; } - } - - /// - /// Override to maintain backwards compatibility with only 4 required length - /// - protected override int DefaultMinPasswordLength - { - get { return 4; } - } - - /// - /// Override to maintain backwards compatibility - /// - protected override bool DefaultUseLegacyEncoding - { - get { return true; } - } - - /// - /// For backwards compatibility, this provider supports this option - /// - internal override bool AllowManuallyChangingPassword - { - get { return true; } - } - - #region Initialization Method - /// - /// Initializes the provider. - /// - /// The friendly name of the provider. - /// A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider. - /// The name of the provider is null. - /// An attempt is made to call - /// on a provider after the provider - /// has already been initialized. - /// The name of the provider has a length of zero. - public override void Initialize(string name, NameValueCollection config) - { - // Intialize values from web.config - if (config == null) throw new ArgumentNullException("config"); - - if (string.IsNullOrEmpty(name)) name = "UmbracoMembershipProvider"; - - base.Initialize(name, config); - - _providerName = name; - - // test for membertype (if not specified, choose the first member type available) - if (config["defaultMemberTypeAlias"] != null) - _defaultMemberTypeAlias = config["defaultMemberTypeAlias"]; - else if (MemberType.GetAll.Length == 1) - _defaultMemberTypeAlias = MemberType.GetAll[0].Alias; - else - throw new ProviderException("No default MemberType alias is specified in the web.config string. Please add a 'defaultMemberTypeAlias' to the add element in the provider declaration in web.config"); - - // test for approve status - if (config["umbracoApprovePropertyTypeAlias"] != null) - { - _approvedPropertyTypeAlias = config["umbracoApprovePropertyTypeAlias"]; - } - // test for lock attempts - if (config["umbracoLockPropertyTypeAlias"] != null) - { - _lockPropertyTypeAlias = config["umbracoLockPropertyTypeAlias"]; - } - if (config["umbracoLastLockedPropertyTypeAlias"] != null) - { - _lastLockedOutPropertyTypeAlias = config["umbracoLastLockedPropertyTypeAlias"]; - } - if (config["umbracoLastPasswordChangedPropertyTypeAlias"] != null) - { - _lastPasswordChangedPropertyTypeAlias = config["umbracoLastPasswordChangedPropertyTypeAlias"]; - } - if (config["umbracoFailedPasswordAttemptsPropertyTypeAlias"] != null) - { - _failedPasswordAttemptsPropertyTypeAlias = config["umbracoFailedPasswordAttemptsPropertyTypeAlias"]; - } - // comment property - if (config["umbracoCommentPropertyTypeAlias"] != null) - { - _commentPropertyTypeAlias = config["umbracoCommentPropertyTypeAlias"]; - } - // last login date - if (config["umbracoLastLoginPropertyTypeAlias"] != null) - { - _lastLoginPropertyTypeAlias = config["umbracoLastLoginPropertyTypeAlias"]; - } - // password retrieval - if (config["umbracoPasswordRetrievalQuestionPropertyTypeAlias"] != null) - { - _passwordRetrievalQuestionPropertyTypeAlias = config["umbracoPasswordRetrievalQuestionPropertyTypeAlias"]; - } - if (config["umbracoPasswordRetrievalAnswerPropertyTypeAlias"] != null) - { - _passwordRetrievalAnswerPropertyTypeAlias = config["umbracoPasswordRetrievalAnswerPropertyTypeAlias"]; - } - - } - #endregion - - #region Methods - - /// - /// Processes a request to update the password for a membership user. - /// - /// The user to update the password for. - /// This property is ignore for this provider - /// The new password for the specified user. - /// - /// true if the password was updated successfully; otherwise, false. - /// - protected override bool PerformChangePassword(string username, string oldPassword, string newPassword) - { - //NOTE: due to backwards compatibilty reasons, this provider doesn't care about the old password and - // allows simply setting the password manually so we don't really care about the old password. - // This is allowed based on the overridden AllowManuallyChangingPassword option. - - // in order to support updating passwords from the umbraco core, we can't validate the old password - var m = Member.GetMemberFromLoginName(username); - if (m == null) return false; - - var args = new ValidatePasswordEventArgs(username, newPassword, false); - OnValidatingPassword(args); - - if (args.Cancel) - { - if (args.FailureInformation != null) - throw args.FailureInformation; - throw new MembershipPasswordException("Change password canceled due to password validation failure."); - } - - string salt; - var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); - m.ChangePassword( - FormatPasswordForStorage(encodedPassword, salt)); - - UpdateMemberProperty(m, _lastPasswordChangedPropertyTypeAlias, DateTime.Now); - - m.Save(); - - return true; - } - - /// - /// Processes a request to update the password question and answer for a membership user. - /// - /// The user to change the password question and answer for. - /// The password for the specified user. - /// The new password question for the specified user. - /// The new password answer for the specified user. - /// - /// true if the password question and answer are updated successfully; otherwise, false. - /// - public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) - { - if (!String.IsNullOrEmpty(_passwordRetrievalQuestionPropertyTypeAlias) && !String.IsNullOrEmpty(_passwordRetrievalAnswerPropertyTypeAlias)) - { - if (ValidateUser(username, password)) - { - Member m = Member.GetMemberFromLoginName(username); - if (m != null) - { - UpdateMemberProperty(m, _passwordRetrievalQuestionPropertyTypeAlias, newPasswordQuestion); - UpdateMemberProperty(m, _passwordRetrievalAnswerPropertyTypeAlias, newPasswordAnswer); - m.Save(); - return true; - } - else - { - throw new MembershipPasswordException("The supplied user is not found!"); - } - } - else { - throw new MembershipPasswordException("Invalid user/password combo"); - } - - } - else - { - throw new NotSupportedException("Updating the password Question and Answer is not valid if the properties aren't set in the config file"); - } - } - - /// - /// Adds a new membership user to the data source. - /// - /// - /// The user name for the new user. - /// The password for the new user. - /// The e-mail address for the new user. - /// The password question for the new user. - /// The password answer for the new user - /// Whether or not the new user is approved to be validated. - /// The unique identifier from the membership data source for the user. - /// A enumeration value indicating whether the user was created successfully. - /// - /// A object populated with the information for the newly created user. - /// - public MembershipUser CreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, - string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) - { - - var args = new ValidatePasswordEventArgs(username, password, true); - OnValidatingPassword(args); - if (args.Cancel) - { - status = MembershipCreateStatus.InvalidPassword; - return null; - } - - if (Member.GetMemberFromLoginName(username) != null) - { - status = MembershipCreateStatus.DuplicateUserName; - } - else if (Member.GetMemberFromEmail(email) != null && RequiresUniqueEmail) - { - status = MembershipCreateStatus.DuplicateEmail; - } - else - { - var memberType = MemberType.GetByAlias(memberTypeAlias); - if (memberType == null) - { - throw new InvalidOperationException("Could not find a member type with alias " + memberTypeAlias + ". Ensure your membership provider configuration is up to date and that the default member type exists."); - } - - var m = Member.MakeNew(username, email, memberType, User.GetUser(0)); - - string salt; - var encodedPassword = EncryptOrHashNewPassword(password, out salt); - //set the password on the member - m.ChangePassword( - FormatPasswordForStorage(encodedPassword, salt)); - - // custom fields - if (string.IsNullOrEmpty(_passwordRetrievalQuestionPropertyTypeAlias) == false) - { - UpdateMemberProperty(m, _passwordRetrievalQuestionPropertyTypeAlias, passwordQuestion); - } - - if (string.IsNullOrEmpty(_passwordRetrievalAnswerPropertyTypeAlias) == false) - { - UpdateMemberProperty(m, _passwordRetrievalAnswerPropertyTypeAlias, passwordAnswer); - } - - if (string.IsNullOrEmpty(ApprovedPropertyTypeAlias) == false) - { - UpdateMemberProperty(m, ApprovedPropertyTypeAlias, isApproved ? 1 : 0); - } - - if (string.IsNullOrEmpty(_lastLoginPropertyTypeAlias) == false) - { - UpdateMemberProperty(m, _lastLoginPropertyTypeAlias, DateTime.Now); - } - - if (string.IsNullOrEmpty(_lastPasswordChangedPropertyTypeAlias) == false) - { - UpdateMemberProperty(m, _lastPasswordChangedPropertyTypeAlias, DateTime.Now); - } - - var mUser = ConvertToMembershipUser(m); - - // save - m.Save(); - - status = MembershipCreateStatus.Success; - - return mUser; - } - return null; - } - - /// - /// Adds a new membership user to the data source. - /// - /// The user name for the new user. - /// The password for the new user. - /// The e-mail address for the new user. - /// The password question for the new user. - /// The password answer for the new user - /// Whether or not the new user is approved to be validated. - /// The unique identifier from the membership data source for the user. - /// A enumeration value indicating whether the user was created successfully. - /// - /// A object populated with the information for the newly created user. - /// - public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, - string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) - { - return CreateUser(_defaultMemberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); - } - - /// - /// Removes a user from the membership data source. - /// - /// The name of the user to delete. - /// true to delete data related to the user from the database; false to leave data related to the user in the database. - /// - /// true if the user was successfully deleted; otherwise, false. - /// - public override bool DeleteUser(string username, bool deleteAllRelatedData) - { - var m = Member.GetMemberFromLoginName(username); - if (m == null) return false; - m.delete(); - return true; - } - - /// - /// Gets a collection of membership users where the e-mail address contains the specified e-mail address to match. - /// - /// The e-mail address to search for. - /// The index of the page of results to return. pageIndex is zero-based. - /// The size of the page of results to return. - /// The total number of matched users. - /// - /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. - /// - public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) - { - var byEmail = ApplicationContext.Current.Services.MemberService.FindMembersByEmail(emailToMatch).ToArray(); - totalRecords = byEmail.Length; - var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); - - var collection = new MembershipUserCollection(); - foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) - { - collection.Add(ConvertToMembershipUser(m)); - } - return collection; - } - - /// - /// Gets a collection of membership users where the user name contains the specified user name to match. - /// - /// The user name to search for. - /// The index of the page of results to return. pageIndex is zero-based. - /// The size of the page of results to return. - /// The total number of matched users. - /// - /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. - /// - public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) - { - var counter = 0; - var startIndex = pageSize * pageIndex; - var endIndex = startIndex + pageSize - 1; - var membersList = new MembershipUserCollection(); - var memberArray = Member.GetMemberByName(usernameToMatch, false); - totalRecords = memberArray.Length; - - foreach (var m in memberArray) - { - if (counter >= startIndex) - membersList.Add(ConvertToMembershipUser(m)); - if (counter >= endIndex) break; - counter++; - } - return membersList; - } - - /// - /// Gets a collection of all the users in the data source in pages of data. - /// - /// The index of the page of results to return. pageIndex is zero-based. - /// The size of the page of results to return. - /// The total number of matched users. - /// - /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. - /// - public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) - { - var counter = 0; - var startIndex = pageSize * pageIndex; - var endIndex = startIndex + pageSize - 1; - var membersList = new MembershipUserCollection(); - var memberArray = Member.GetAll; - totalRecords = memberArray.Length; - - foreach (var m in memberArray) - { - if (counter >= startIndex) - membersList.Add(ConvertToMembershipUser(m)); - if (counter >= endIndex) break; - counter++; - } - return membersList; - - } - - /// - /// Gets the number of users currently accessing the application. - /// - /// - /// The number of users currently accessing the application. - /// - public override int GetNumberOfUsersOnline() - { - return Member.CachedMembers().Count; - } - - /// - /// Gets the password for the specified user name from the data source. - /// - /// The user to retrieve the password for. - /// The password answer for the user. - /// - /// The password for the specified user name. - /// - public override string GetPassword(string username, string answer) - { - if (EnablePasswordRetrieval == false) - throw new ProviderException("Password Retrieval Not Enabled."); - - if (PasswordFormat == MembershipPasswordFormat.Hashed) - throw new ProviderException("Cannot retrieve Hashed passwords."); - - var m = Member.GetMemberFromLoginName(username); - if (m != null) - { - if (RequiresQuestionAndAnswer) - { - // check if password answer property alias is set - if (string.IsNullOrEmpty(_passwordRetrievalAnswerPropertyTypeAlias) == false) - { - // check if user is locked out - if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) - { - var isLockedOut = false; - bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLockedOut); - if (isLockedOut) - { - throw new MembershipPasswordException("The supplied user is locked out"); - } - } - - // match password answer - if (GetMemberProperty(m, _passwordRetrievalAnswerPropertyTypeAlias, false) != answer) - { - throw new MembershipPasswordException("Incorrect password answer"); - } - } - else - { - throw new ProviderException("Password retrieval answer property alias is not set! To automatically support password question/answers you'll need to add references to the membertype properties in the 'Member' element in web.config by adding their aliases to the 'umbracoPasswordRetrievalQuestionPropertyTypeAlias' and 'umbracoPasswordRetrievalAnswerPropertyTypeAlias' attributes"); - } - } - } - if (m == null) - { - throw new MembershipPasswordException("The supplied user is not found"); - } - return m.GetPassword(); - } - - /// - /// Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user. - /// - /// The name of the user to get information for. - /// true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user. - /// - /// A object populated with the specified user's information from the data source. - /// - public override MembershipUser GetUser(string username, bool userIsOnline) - { - if (String.IsNullOrEmpty(username)) - return null; - Member m = Member.GetMemberFromLoginName(username); - if (m == null) return null; - else return ConvertToMembershipUser(m); - } - - /// - /// Gets information from the data source for a user based on the unique identifier for the membership user. Provides an option to update the last-activity date/time stamp for the user. - /// - /// The unique identifier for the membership user to get information for. - /// true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user. - /// - /// A object populated with the specified user's information from the data source. - /// - public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) - { - var asGuid = providerUserKey.TryConvertTo(); - if (asGuid.Success) - { - var m = new Member(asGuid.Result); - return ConvertToMembershipUser(m); - } - var asInt = providerUserKey.TryConvertTo(); - if (asInt.Success) - { - var m = new Member(asInt.Result); - return ConvertToMembershipUser(m); - } - throw new InvalidOperationException("The " + GetType() + " provider only supports GUID or Int as a providerUserKey"); - - } - - - /// - /// Gets the user name associated with the specified e-mail address. - /// - /// The e-mail address to search for. - /// - /// The user name associated with the specified e-mail address. If no match is found, return null. - /// - public override string GetUserNameByEmail(string email) - { - Member m = Member.GetMemberFromEmail(email); - return m == null ? null : m.LoginName; - } - - /// - /// Resets a user's password to a new, automatically generated password. - /// - /// The user to reset the password for. - /// The password answer for the specified user (not used with Umbraco). - /// The new password for the specified user. - public override string ResetPassword(string username, string answer) - { - if (EnablePasswordReset == false) - { - throw new NotSupportedException("Password reset is not supported"); - } - - //TODO: This should be here - but how do we update failure count in this provider?? - //if (answer == null && RequiresQuestionAndAnswer) - //{ - // UpdateFailureCount(username, "passwordAnswer"); - - // throw new ProviderException("Password answer required for password reset."); - //} - - var newPassword = Membership.GeneratePassword(MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters); - - var args = new ValidatePasswordEventArgs(username, newPassword, true); - OnValidatingPassword(args); - if (args.Cancel) - { - if (args.FailureInformation != null) - throw args.FailureInformation; - throw new MembershipPasswordException("Reset password canceled due to password validation failure."); - } - - var m = Member.GetMemberFromLoginName(username); - if (m == null) - throw new MembershipPasswordException("The supplied user is not found"); - - if (RequiresQuestionAndAnswer) - { - // check if password answer property alias is set - if (string.IsNullOrEmpty(_passwordRetrievalAnswerPropertyTypeAlias) == false) - { - // check if user is locked out - if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) - { - var isLockedOut = false; - bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLockedOut); - if (isLockedOut) - { - throw new MembershipPasswordException("The supplied user is locked out"); - } - } - - // match password answer - if (GetMemberProperty(m, _passwordRetrievalAnswerPropertyTypeAlias, false) != answer) - { - throw new MembershipPasswordException("Incorrect password answer"); - } - } - else - { - throw new ProviderException("Password retrieval answer property alias is not set! To automatically support password question/answers you'll need to add references to the membertype properties in the 'Member' element in web.config by adding their aliases to the 'umbracoPasswordRetrievalQuestionPropertyTypeAlias' and 'umbracoPasswordRetrievalAnswerPropertyTypeAlias' attributes"); - } - } - - string salt; - var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); - //set the password on the member - m.ChangePassword( - FormatPasswordForStorage(encodedPassword, salt)); - - if (string.IsNullOrEmpty(_lastPasswordChangedPropertyTypeAlias) == false) - { - UpdateMemberProperty(m, _lastPasswordChangedPropertyTypeAlias, DateTime.Now); - } - - m.Save(); - - return newPassword; - } - - /// - /// Clears a lock so that the membership user can be validated. - /// - /// The membership user to clear the lock status for. - /// - /// true if the membership user was successfully unlocked; otherwise, false. - /// - public override bool UnlockUser(string userName) - { - if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) - { - var m = Member.GetMemberFromLoginName(userName); - if (m != null) - { - UpdateMemberProperty(m, LockPropertyTypeAlias, 0); - m.Save(); - return true; - } - throw new Exception(String.Format("No member with the username '{0}' found", userName)); - } - throw new ProviderException("To enable lock/unlocking, you need to add a 'bool' property on your membertype and add the alias of the property in the 'umbracoLockPropertyTypeAlias' attribute of the membership element in the web.config."); - } - - /// - /// Updates e-mail and potentially approved status, lock status and comment on a user. - /// - /// A object that represents the user to update and the updated information for the user. - public override void UpdateUser(MembershipUser user) - { - var m = Member.GetMemberFromLoginName(user.UserName); - m.Email = user.Email; - - // if supported, update approve status - UpdateMemberProperty(m, ApprovedPropertyTypeAlias, user.IsApproved ? 1 : 0); - - // if supported, update lock status - UpdateMemberProperty(m, LockPropertyTypeAlias, user.IsLockedOut ? 1 : 0); - if (user.IsLockedOut) - { - UpdateMemberProperty(m, _lastLockedOutPropertyTypeAlias, DateTime.Now); - } - - // if supported, update comment - UpdateMemberProperty(m, CommentPropertyTypeAlias, user.Comment); - - m.Save(); - } - - private static void UpdateMemberProperty(Member m, string propertyAlias, object propertyValue) - { - if (string.IsNullOrEmpty(propertyAlias) == false) - { - if (m.getProperty(propertyAlias) != null) - { - m.getProperty(propertyAlias).Value = propertyValue; - } - } - } - - private static string GetMemberProperty(Member m, string propertyAlias, bool isBool) - { - if (string.IsNullOrEmpty(propertyAlias) == false) - { - if (m.getProperty(propertyAlias) != null && - m.getProperty(propertyAlias).Value != null) - { - if (isBool) - { - // Umbraco stored true as 1, which means it can be bool.tryParse'd - return m.getProperty(propertyAlias).Value.ToString().Replace("1", "true").Replace("0", "false"); - } - return m.getProperty(propertyAlias).Value.ToString(); - } - } - - return null; - } - - private static string GetMemberProperty(IMember m, string propertyAlias, bool isBool) - { - if (string.IsNullOrEmpty(propertyAlias) == false) - { - if (m.Properties[propertyAlias] != null && - m.Properties[propertyAlias].Value != null) - { - if (isBool) - { - // Umbraco stored true as 1, which means it can be bool.tryParse'd - return m.Properties[propertyAlias].Value.ToString().Replace("1", "true").Replace("0", "false"); - } - return m.Properties[propertyAlias].Value.ToString(); - } - } - - return null; - } - - /// - /// Verifies that the specified user name and password exist in the data source. - /// - /// The name of the user to validate. - /// The password for the specified user. - /// - /// true if the specified username and password are valid; otherwise, false. - /// - public override bool ValidateUser(string username, string password) - { - var m = Member.GetMemberFromLoginAndEncodedPassword(username, EncryptOrHashExistingPassword(password)); - if (m != null) - { - // check for lock status. If locked, then set the member property to null - if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) - { - string lockedStatus = GetMemberProperty(m, LockPropertyTypeAlias, true); - if (string.IsNullOrEmpty(lockedStatus) == false) - { - var isLocked = false; - if (bool.TryParse(lockedStatus, out isLocked)) - { - if (isLocked) - { - m = null; - } - } - } - } - - //check for approve status. If not approved, then set the member property to null - if (m != null && !CheckApproveStatus(m)) { - m = null; - } - - // maybe update login date - if (m != null && string.IsNullOrEmpty(_lastLoginPropertyTypeAlias) == false) - { - UpdateMemberProperty(m, _lastLoginPropertyTypeAlias, DateTime.Now); - } - - // maybe reset password attempts - if (m != null && string.IsNullOrEmpty(_failedPasswordAttemptsPropertyTypeAlias) == false) - { - UpdateMemberProperty(m, _failedPasswordAttemptsPropertyTypeAlias, 0); - } - - // persist data - if (m != null) - m.Save(); - } - else if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false - && string.IsNullOrEmpty(_failedPasswordAttemptsPropertyTypeAlias) == false) - { - var updateMemberDataObject = Member.GetMemberFromLoginName(username); - // update fail rate if it's approved - if (updateMemberDataObject != null && CheckApproveStatus(updateMemberDataObject)) - { - int failedAttempts = 0; - int.TryParse(GetMemberProperty(updateMemberDataObject, _failedPasswordAttemptsPropertyTypeAlias, false), out failedAttempts); - failedAttempts = failedAttempts+1; - UpdateMemberProperty(updateMemberDataObject, _failedPasswordAttemptsPropertyTypeAlias, failedAttempts); - - // lock user? - if (failedAttempts >= MaxInvalidPasswordAttempts) - { - UpdateMemberProperty(updateMemberDataObject, LockPropertyTypeAlias, 1); - UpdateMemberProperty(updateMemberDataObject, _lastLockedOutPropertyTypeAlias, DateTime.Now); - } - updateMemberDataObject.Save(); - } - - } - return (m != null); - } - - private bool CheckApproveStatus(Member m) - { - var isApproved = false; - if (string.IsNullOrEmpty(ApprovedPropertyTypeAlias) == false) - { - if (m != null) - { - var approveStatus = GetMemberProperty(m, ApprovedPropertyTypeAlias, true); - if (string.IsNullOrEmpty(approveStatus) == false) - { - //try parsing as bool first (just in case) - if (bool.TryParse(approveStatus, out isApproved) == false) - { - int intStatus; - //if that fails, try parsing as int (since its normally stored as 0 or 1) - if (int.TryParse(approveStatus, out intStatus)) - { - isApproved = intStatus != 0; - } - } - } - else - { - //There is no property so we shouldn't use the approve status - isApproved = true; - } - } - } - else { - // if we don't use approve statuses - isApproved = true; - } - return isApproved; - } - #endregion - - #region Helper Methods - - /// - /// Checks the password. - /// - /// The password. - /// The dbPassword. - /// - internal bool CheckPassword(string password, string dbPassword) - { - string pass1 = password; - string pass2 = dbPassword; - - switch (PasswordFormat) - { - case MembershipPasswordFormat.Encrypted: - pass2 = DecodePassword(dbPassword); - break; - case MembershipPasswordFormat.Hashed: - pass1 = EncryptOrHashExistingPassword(password); - break; - default: - break; - } - return (pass1 == pass2) ? true : false; - } - - - /// - /// Encodes the password. - /// - /// The password. - /// The encoded password. - [Obsolete("Do not use this, it is the legacy way to encode a password")] - public string EncodePassword(string password) - { - return LegacyEncodePassword(password); - } - - /// - /// Unencode password. - /// - /// The encoded password. - /// The unencoded password. - [Obsolete("Do not use this, it is the legacy way to decode a password")] - public string UnEncodePassword(string encodedPassword) - { - return LegacyUnEncodePassword(encodedPassword); - } - - /// - /// Converts to membership user. - /// - /// The m. - /// - private MembershipUser ConvertToMembershipUser(Member m) - { - if (m == null) return null; - - var lastLogin = DateTime.Now; - var lastLocked = DateTime.MinValue; - var isApproved = true; - var isLocked = false; - var comment = ""; - var passwordQuestion = ""; - - // last login - if (string.IsNullOrEmpty(_lastLoginPropertyTypeAlias) == false) - { - DateTime.TryParse(GetMemberProperty(m, _lastLoginPropertyTypeAlias, false), out lastLogin); - } - // approved - if (string.IsNullOrEmpty(ApprovedPropertyTypeAlias) == false) - { - bool.TryParse(GetMemberProperty(m, ApprovedPropertyTypeAlias, true), out isApproved); - } - // locked - if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) - { - bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLocked); - } - // last locked - if (string.IsNullOrEmpty(_lastLockedOutPropertyTypeAlias) == false) - { - DateTime.TryParse(GetMemberProperty(m, _lastLockedOutPropertyTypeAlias, false), out lastLocked); - } - // comment - if (string.IsNullOrEmpty(CommentPropertyTypeAlias) == false) - { - comment = GetMemberProperty(m, CommentPropertyTypeAlias, false); - } - // password question - if (string.IsNullOrEmpty(_passwordRetrievalQuestionPropertyTypeAlias) == false) - { - passwordQuestion = GetMemberProperty(m, _passwordRetrievalQuestionPropertyTypeAlias, false); - } - - return new MembershipUser(_providerName, m.LoginName, m.Id, m.Email, passwordQuestion, comment, isApproved, isLocked, m.CreateDateTime, lastLogin, - DateTime.Now, DateTime.Now, lastLocked); - } - - /// - /// Converts to membership user. - /// - /// The m. - /// - private MembershipUser ConvertToMembershipUser(IMember m) - { - if (m == null) return null; - - var lastLogin = DateTime.Now; - var lastLocked = DateTime.MinValue; - var isApproved = true; - var isLocked = false; - var comment = ""; - var passwordQuestion = ""; - - // last login - if (string.IsNullOrEmpty(_lastLoginPropertyTypeAlias) == false) - { - DateTime.TryParse(GetMemberProperty(m, _lastLoginPropertyTypeAlias, false), out lastLogin); - } - // approved - if (string.IsNullOrEmpty(ApprovedPropertyTypeAlias) == false) - { - bool.TryParse(GetMemberProperty(m, ApprovedPropertyTypeAlias, true), out isApproved); - } - // locked - if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) - { - bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLocked); - } - // last locked - if (string.IsNullOrEmpty(_lastLockedOutPropertyTypeAlias) == false) - { - DateTime.TryParse(GetMemberProperty(m, _lastLockedOutPropertyTypeAlias, false), out lastLocked); - } - // comment - if (string.IsNullOrEmpty(CommentPropertyTypeAlias) == false) - { - comment = GetMemberProperty(m, CommentPropertyTypeAlias, false); - } - // password question - if (string.IsNullOrEmpty(_passwordRetrievalQuestionPropertyTypeAlias) == false) - { - passwordQuestion = GetMemberProperty(m, _passwordRetrievalQuestionPropertyTypeAlias, false); - } - - return new MembershipUser(_providerName, m.Username, m.Id, m.Email, passwordQuestion, comment, isApproved, isLocked, m.CreateDate, lastLogin, - DateTime.Now, DateTime.Now, lastLocked); - } - - #endregion - } -} +#region namespace +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web.Security; +using System.Configuration; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Security; +using umbraco.BusinessLogic; +using System.Security.Cryptography; +using System.Web.Util; +using System.Collections.Specialized; +using System.Configuration.Provider; +using System.Security; +using System.Security.Permissions; +using System.Runtime.CompilerServices; +using Member = umbraco.cms.businesslogic.member.Member; +using MemberType = umbraco.cms.businesslogic.member.MemberType; + +#endregion + +namespace umbraco.providers.members +{ + /// + /// Custom Membership Provider for Umbraco Members (User authentication for Frontend applications NOT umbraco CMS) + /// + + public class UmbracoMembershipProvider : UmbracoMembershipProviderBase + { + #region Fields + + private string _providerName = Member.UmbracoMemberProviderName; + + #endregion + + /// + /// Override to maintain backwards compatibility with 0 required non-alphanumeric chars + /// + public override int DefaultMinNonAlphanumericChars + { + get { return 0; } + } + + /// + /// Override to maintain backwards compatibility with only 4 required length + /// + public override int DefaultMinPasswordLength + { + get { return 4; } + } + + /// + /// Override to maintain backwards compatibility + /// + public override bool DefaultUseLegacyEncoding + { + get { return true; } + } + + /// + /// For backwards compatibility, this provider supports this option + /// + public override bool AllowManuallyChangingPassword + { + get { return true; } + } + + #region Initialization Method + /// + /// Initializes the provider. + /// + /// The friendly name of the provider. + /// A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider. + /// The name of the provider is null. + /// An attempt is made to call + /// on a provider after the provider + /// has already been initialized. + /// The name of the provider has a length of zero. + public override void Initialize(string name, NameValueCollection config) + { + // Intialize values from web.config + if (config == null) throw new ArgumentNullException("config"); + + if (string.IsNullOrEmpty(name)) name = "UmbracoMembershipProvider"; + + base.Initialize(name, config); + + _providerName = name; + + // test for membertype (if not specified, choose the first member type available) + if (config["defaultMemberTypeAlias"] != null) + DefaultMemberTypeAlias = config["defaultMemberTypeAlias"]; + else if (MemberType.GetAll.Length == 1) + DefaultMemberTypeAlias = MemberType.GetAll[0].Alias; + else + throw new ProviderException("No default MemberType alias is specified in the web.config string. Please add a 'defaultMemberTypeAlias' to the add element in the provider declaration in web.config"); + + // test for approve status + if (config["umbracoApprovePropertyTypeAlias"] != null) + { + ApprovedPropertyTypeAlias = config["umbracoApprovePropertyTypeAlias"]; + } + // test for lock attempts + if (config["umbracoLockPropertyTypeAlias"] != null) + { + LockPropertyTypeAlias = config["umbracoLockPropertyTypeAlias"]; + } + if (config["umbracoLastLockedPropertyTypeAlias"] != null) + { + LastLockedOutPropertyTypeAlias = config["umbracoLastLockedPropertyTypeAlias"]; + } + if (config["umbracoLastPasswordChangedPropertyTypeAlias"] != null) + { + LastPasswordChangedPropertyTypeAlias = config["umbracoLastPasswordChangedPropertyTypeAlias"]; + } + if (config["umbracoFailedPasswordAttemptsPropertyTypeAlias"] != null) + { + FailedPasswordAttemptsPropertyTypeAlias = config["umbracoFailedPasswordAttemptsPropertyTypeAlias"]; + } + // comment property + if (config["umbracoCommentPropertyTypeAlias"] != null) + { + CommentPropertyTypeAlias = config["umbracoCommentPropertyTypeAlias"]; + } + // last login date + if (config["umbracoLastLoginPropertyTypeAlias"] != null) + { + LastLoginPropertyTypeAlias = config["umbracoLastLoginPropertyTypeAlias"]; + } + // password retrieval + if (config["umbracoPasswordRetrievalQuestionPropertyTypeAlias"] != null) + { + PasswordRetrievalQuestionPropertyTypeAlias = config["umbracoPasswordRetrievalQuestionPropertyTypeAlias"]; + } + if (config["umbracoPasswordRetrievalAnswerPropertyTypeAlias"] != null) + { + PasswordRetrievalAnswerPropertyTypeAlias = config["umbracoPasswordRetrievalAnswerPropertyTypeAlias"]; + } + + } + #endregion + + #region Methods + + /// + /// Processes a request to update the password for a membership user. + /// + /// The user to update the password for. + /// This property is ignore for this provider + /// The new password for the specified user. + /// + /// true if the password was updated successfully; otherwise, false. + /// + protected override bool PerformChangePassword(string username, string oldPassword, string newPassword) + { + //NOTE: due to backwards compatibilty reasons, this provider doesn't care about the old password and + // allows simply setting the password manually so we don't really care about the old password. + // This is allowed based on the overridden AllowManuallyChangingPassword option. + + // in order to support updating passwords from the umbraco core, we can't validate the old password + var m = Member.GetMemberFromLoginName(username); + if (m == null) return false; + + var args = new ValidatePasswordEventArgs(username, newPassword, false); + OnValidatingPassword(args); + + if (args.Cancel) + { + if (args.FailureInformation != null) + throw args.FailureInformation; + throw new MembershipPasswordException("Change password canceled due to password validation failure."); + } + + string salt; + var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); + m.ChangePassword( + FormatPasswordForStorage(encodedPassword, salt)); + + UpdateMemberProperty(m, LastPasswordChangedPropertyTypeAlias, DateTime.Now); + + m.Save(); + + return true; + } + + /// + /// Processes a request to update the password question and answer for a membership user. + /// + /// The user to change the password question and answer for. + /// The password for the specified user. + /// The new password question for the specified user. + /// The new password answer for the specified user. + /// + /// true if the password question and answer are updated successfully; otherwise, false. + /// + protected override bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) + { + var m = Member.GetMemberFromLoginName(username); + if (m == null) + { + return false; + } + + UpdateMemberProperty(m, PasswordRetrievalQuestionPropertyTypeAlias, newPasswordQuestion); + UpdateMemberProperty(m, PasswordRetrievalAnswerPropertyTypeAlias, newPasswordAnswer); + m.Save(); + return true; + } + + /// + /// Adds a new membership user to the data source. + /// + /// + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + protected override MembershipUser PerformCreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, + string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + { + if (Member.GetMemberFromLoginName(username) != null) + { + status = MembershipCreateStatus.DuplicateUserName; + LogHelper.Warn("Cannot create member as username already exists: " + username); + return null; + } + + if (Member.GetMemberFromEmail(email) != null && RequiresUniqueEmail) + { + status = MembershipCreateStatus.DuplicateEmail; + LogHelper.Warn( + "Cannot create member as a member with the same email address exists: " + email); + return null; + } + + var memberType = MemberType.GetByAlias(memberTypeAlias); + if (memberType == null) + { + throw new InvalidOperationException("Could not find a member type with alias " + memberTypeAlias + ". Ensure your membership provider configuration is up to date and that the default member type exists."); + } + + var m = Member.MakeNew(username, email, memberType, User.GetUser(0)); + + string salt; + var encodedPassword = EncryptOrHashNewPassword(password, out salt); + + //set the password on the member + m.ChangePassword(FormatPasswordForStorage(encodedPassword, salt)); + + // custom fields + if (string.IsNullOrEmpty(PasswordRetrievalQuestionPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, PasswordRetrievalQuestionPropertyTypeAlias, passwordQuestion); + } + + if (string.IsNullOrEmpty(PasswordRetrievalAnswerPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, PasswordRetrievalAnswerPropertyTypeAlias, passwordAnswer); + } + + if (string.IsNullOrEmpty(ApprovedPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, ApprovedPropertyTypeAlias, isApproved ? 1 : 0); + } + + if (string.IsNullOrEmpty(LastLoginPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, LastLoginPropertyTypeAlias, DateTime.Now); + } + + if (string.IsNullOrEmpty(LastPasswordChangedPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, LastPasswordChangedPropertyTypeAlias, DateTime.Now); + } + + var mUser = ConvertToMembershipUser(m); + + // save + m.Save(); + + status = MembershipCreateStatus.Success; + + return mUser; + } + + + /// + /// Removes a user from the membership data source. + /// + /// The name of the user to delete. + /// + /// TODO: This setting currently has no effect + /// + /// + /// true if the user was successfully deleted; otherwise, false. + /// + public override bool DeleteUser(string username, bool deleteAllRelatedData) + { + var m = Member.GetMemberFromLoginName(username); + if (m == null) return false; + m.delete(); + return true; + } + + /// + /// Gets a collection of membership users where the e-mail address contains the specified e-mail address to match. + /// + /// The e-mail address to search for. + /// The index of the page of results to return. pageIndex is zero-based. + /// The size of the page of results to return. + /// The total number of matched users. + /// + /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. + /// + public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) + { + var byEmail = ApplicationContext.Current.Services.MemberService.FindMembersByEmail(emailToMatch, StringPropertyMatchType.Wildcard).ToArray(); + totalRecords = byEmail.Length; + var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); + + var collection = new MembershipUserCollection(); + foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + { + collection.Add(ConvertToMembershipUser(m)); + } + return collection; + } + + /// + /// Gets a collection of membership users where the user name contains the specified user name to match. + /// + /// The user name to search for. + /// The index of the page of results to return. pageIndex is zero-based. + /// The size of the page of results to return. + /// The total number of matched users. + /// + /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. + /// + public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) + { + var byEmail = ApplicationContext.Current.Services.MemberService.FindMembersByUsername(usernameToMatch, StringPropertyMatchType.Wildcard).ToArray(); + totalRecords = byEmail.Length; + var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); + + var collection = new MembershipUserCollection(); + foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + { + collection.Add(ConvertToMembershipUser(m)); + } + return collection; + } + + /// + /// Gets a collection of all the users in the data source in pages of data. + /// + /// The index of the page of results to return. pageIndex is zero-based. + /// The size of the page of results to return. + /// The total number of matched users. + /// + /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. + /// + public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) + { + var counter = 0; + var startIndex = pageSize * pageIndex; + var endIndex = startIndex + pageSize - 1; + var membersList = new MembershipUserCollection(); + var memberArray = Member.GetAll; + totalRecords = memberArray.Length; + + foreach (var m in memberArray) + { + if (counter >= startIndex) + membersList.Add(ConvertToMembershipUser(m)); + if (counter >= endIndex) break; + counter++; + } + return membersList; + + } + + /// + /// Gets the number of users currently accessing the application. + /// + /// + /// The number of users currently accessing the application. + /// + public override int GetNumberOfUsersOnline() + { + return Member.CachedMembers().Count; + } + + /// + /// Gets the password for the specified user name from the data source. + /// + /// The user to retrieve the password for. + /// The password answer for the user. + /// + /// The password for the specified user name. + /// + public override string GetPassword(string username, string answer) + { + if (EnablePasswordRetrieval == false) + throw new ProviderException("Password Retrieval Not Enabled."); + + if (PasswordFormat == MembershipPasswordFormat.Hashed) + throw new ProviderException("Cannot retrieve Hashed passwords."); + + var m = Member.GetMemberFromLoginName(username); + if (m != null) + { + if (RequiresQuestionAndAnswer) + { + // check if password answer property alias is set + if (string.IsNullOrEmpty(PasswordRetrievalAnswerPropertyTypeAlias) == false) + { + // check if user is locked out + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) + { + var isLockedOut = false; + bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLockedOut); + if (isLockedOut) + { + throw new MembershipPasswordException("The supplied user is locked out"); + } + } + + // match password answer + if (GetMemberProperty(m, PasswordRetrievalAnswerPropertyTypeAlias, false) != answer) + { + throw new MembershipPasswordException("Incorrect password answer"); + } + } + else + { + throw new ProviderException("Password retrieval answer property alias is not set! To automatically support password question/answers you'll need to add references to the membertype properties in the 'Member' element in web.config by adding their aliases to the 'umbracoPasswordRetrievalQuestionPropertyTypeAlias' and 'umbracoPasswordRetrievalAnswerPropertyTypeAlias' attributes"); + } + } + } + if (m == null) + { + throw new MembershipPasswordException("The supplied user is not found"); + } + return m.GetPassword(); + } + + /// + /// Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user. + /// + /// The name of the user to get information for. + /// true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user. + /// + /// A object populated with the specified user's information from the data source. + /// + public override MembershipUser GetUser(string username, bool userIsOnline) + { + if (String.IsNullOrEmpty(username)) + return null; + Member m = Member.GetMemberFromLoginName(username); + if (m == null) return null; + else return ConvertToMembershipUser(m); + } + + /// + /// Gets information from the data source for a user based on the unique identifier for the membership user. Provides an option to update the last-activity date/time stamp for the user. + /// + /// The unique identifier for the membership user to get information for. + /// true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user. + /// + /// A object populated with the specified user's information from the data source. + /// + public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) + { + var asGuid = providerUserKey.TryConvertTo(); + if (asGuid.Success) + { + var m = new Member(asGuid.Result); + return ConvertToMembershipUser(m); + } + var asInt = providerUserKey.TryConvertTo(); + if (asInt.Success) + { + var m = new Member(asInt.Result); + return ConvertToMembershipUser(m); + } + throw new InvalidOperationException("The " + GetType() + " provider only supports GUID or Int as a providerUserKey"); + + } + + + /// + /// Gets the user name associated with the specified e-mail address. + /// + /// The e-mail address to search for. + /// + /// The user name associated with the specified e-mail address. If no match is found, return null. + /// + public override string GetUserNameByEmail(string email) + { + Member m = Member.GetMemberFromEmail(email); + return m == null ? null : m.LoginName; + } + + /// + /// Resets a user's password to a new, automatically generated password. + /// + /// The user to reset the password for. + /// The password answer for the specified user (not used with Umbraco). + /// The new password for the specified user. + public override string ResetPassword(string username, string answer) + { + if (EnablePasswordReset == false) + { + throw new NotSupportedException("Password reset is not supported"); + } + + //TODO: This should be here - but how do we update failure count in this provider?? + //if (answer == null && RequiresQuestionAndAnswer) + //{ + // UpdateFailureCount(username, "passwordAnswer"); + + // throw new ProviderException("Password answer required for password reset."); + //} + + var newPassword = Membership.GeneratePassword(MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters); + + var args = new ValidatePasswordEventArgs(username, newPassword, true); + OnValidatingPassword(args); + if (args.Cancel) + { + if (args.FailureInformation != null) + throw args.FailureInformation; + throw new MembershipPasswordException("Reset password canceled due to password validation failure."); + } + + var m = Member.GetMemberFromLoginName(username); + if (m == null) + throw new MembershipPasswordException("The supplied user is not found"); + + if (RequiresQuestionAndAnswer) + { + // check if password answer property alias is set + if (string.IsNullOrEmpty(PasswordRetrievalAnswerPropertyTypeAlias) == false) + { + // check if user is locked out + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) + { + var isLockedOut = false; + bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLockedOut); + if (isLockedOut) + { + throw new MembershipPasswordException("The supplied user is locked out"); + } + } + + // match password answer + if (GetMemberProperty(m, PasswordRetrievalAnswerPropertyTypeAlias, false) != answer) + { + throw new MembershipPasswordException("Incorrect password answer"); + } + } + else + { + throw new ProviderException("Password retrieval answer property alias is not set! To automatically support password question/answers you'll need to add references to the membertype properties in the 'Member' element in web.config by adding their aliases to the 'umbracoPasswordRetrievalQuestionPropertyTypeAlias' and 'umbracoPasswordRetrievalAnswerPropertyTypeAlias' attributes"); + } + } + + string salt; + var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); + //set the password on the member + m.ChangePassword( + FormatPasswordForStorage(encodedPassword, salt)); + + if (string.IsNullOrEmpty(LastPasswordChangedPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, LastPasswordChangedPropertyTypeAlias, DateTime.Now); + } + + m.Save(); + + return newPassword; + } + + /// + /// Clears a lock so that the membership user can be validated. + /// + /// The membership user to clear the lock status for. + /// + /// true if the membership user was successfully unlocked; otherwise, false. + /// + public override bool UnlockUser(string userName) + { + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) + { + var m = Member.GetMemberFromLoginName(userName); + if (m != null) + { + UpdateMemberProperty(m, LockPropertyTypeAlias, 0); + m.Save(); + return true; + } + throw new Exception(String.Format("No member with the username '{0}' found", userName)); + } + throw new ProviderException("To enable lock/unlocking, you need to add a 'bool' property on your membertype and add the alias of the property in the 'umbracoLockPropertyTypeAlias' attribute of the membership element in the web.config."); + } + + /// + /// Updates e-mail and potentially approved status, lock status and comment on a user. + /// + /// A object that represents the user to update and the updated information for the user. + public override void UpdateUser(MembershipUser user) + { + var m = Member.GetMemberFromLoginName(user.UserName); + m.Email = user.Email; + + // if supported, update approve status + UpdateMemberProperty(m, ApprovedPropertyTypeAlias, user.IsApproved ? 1 : 0); + + // if supported, update lock status + UpdateMemberProperty(m, LockPropertyTypeAlias, user.IsLockedOut ? 1 : 0); + if (user.IsLockedOut) + { + UpdateMemberProperty(m, LastLockedOutPropertyTypeAlias, DateTime.Now); + } + + // if supported, update comment + UpdateMemberProperty(m, CommentPropertyTypeAlias, user.Comment); + + m.Save(); + } + + private static void UpdateMemberProperty(Member m, string propertyTypeAlias, object propertyValue) + { + if (string.IsNullOrEmpty(propertyTypeAlias) == false) + { + if (m.getProperty(propertyTypeAlias) != null) + { + m.getProperty(propertyTypeAlias).Value = propertyValue; + } + } + } + + private static string GetMemberProperty(Member m, string propertyTypeAlias, bool isBool) + { + if (string.IsNullOrEmpty(propertyTypeAlias) == false) + { + if (m.getProperty(propertyTypeAlias) != null && + m.getProperty(propertyTypeAlias).Value != null) + { + if (isBool) + { + // Umbraco stored true as 1, which means it can be bool.tryParse'd + return m.getProperty(propertyTypeAlias).Value.ToString().Replace("1", "true").Replace("0", "false"); + } + return m.getProperty(propertyTypeAlias).Value.ToString(); + } + } + + return null; + } + + private static string GetMemberProperty(IMember m, string propertyTypeAlias, bool isBool) + { + if (string.IsNullOrEmpty(propertyTypeAlias) == false) + { + if (m.Properties[propertyTypeAlias] != null && + m.Properties[propertyTypeAlias].Value != null) + { + if (isBool) + { + // Umbraco stored true as 1, which means it can be bool.tryParse'd + return m.Properties[propertyTypeAlias].Value.ToString().Replace("1", "true").Replace("0", "false"); + } + return m.Properties[propertyTypeAlias].Value.ToString(); + } + } + + return null; + } + + /// + /// Verifies that the specified user name and password exist in the data source. + /// + /// The name of the user to validate. + /// The password for the specified user. + /// + /// true if the specified username and password are valid; otherwise, false. + /// + public override bool ValidateUser(string username, string password) + { + var m = Member.GetMemberFromLoginAndEncodedPassword(username, EncryptOrHashExistingPassword(password)); + if (m != null) + { + // check for lock status. If locked, then set the member property to null + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) + { + string lockedStatus = GetMemberProperty(m, LockPropertyTypeAlias, true); + if (string.IsNullOrEmpty(lockedStatus) == false) + { + var isLocked = false; + if (bool.TryParse(lockedStatus, out isLocked)) + { + if (isLocked) + { + m = null; + } + } + } + } + + //check for approve status. If not approved, then set the member property to null + if (m != null && !CheckApproveStatus(m)) { + m = null; + } + + // maybe update login date + if (m != null && string.IsNullOrEmpty(LastLoginPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, LastLoginPropertyTypeAlias, DateTime.Now); + } + + // maybe reset password attempts + if (m != null && string.IsNullOrEmpty(FailedPasswordAttemptsPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, FailedPasswordAttemptsPropertyTypeAlias, 0); + } + + // persist data + if (m != null) + m.Save(); + } + else if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false + && string.IsNullOrEmpty(FailedPasswordAttemptsPropertyTypeAlias) == false) + { + var updateMemberDataObject = Member.GetMemberFromLoginName(username); + // update fail rate if it's approved + if (updateMemberDataObject != null && CheckApproveStatus(updateMemberDataObject)) + { + int failedAttempts = 0; + int.TryParse(GetMemberProperty(updateMemberDataObject, FailedPasswordAttemptsPropertyTypeAlias, false), out failedAttempts); + failedAttempts = failedAttempts+1; + UpdateMemberProperty(updateMemberDataObject, FailedPasswordAttemptsPropertyTypeAlias, failedAttempts); + + // lock user? + if (failedAttempts >= MaxInvalidPasswordAttempts) + { + UpdateMemberProperty(updateMemberDataObject, LockPropertyTypeAlias, 1); + UpdateMemberProperty(updateMemberDataObject, LastLockedOutPropertyTypeAlias, DateTime.Now); + } + updateMemberDataObject.Save(); + } + + } + return (m != null); + } + + private bool CheckApproveStatus(Member m) + { + var isApproved = false; + if (string.IsNullOrEmpty(ApprovedPropertyTypeAlias) == false) + { + if (m != null) + { + var approveStatus = GetMemberProperty(m, ApprovedPropertyTypeAlias, true); + if (string.IsNullOrEmpty(approveStatus) == false) + { + //try parsing as bool first (just in case) + if (bool.TryParse(approveStatus, out isApproved) == false) + { + int intStatus; + //if that fails, try parsing as int (since its normally stored as 0 or 1) + if (int.TryParse(approveStatus, out intStatus)) + { + isApproved = intStatus != 0; + } + } + } + else + { + //There is no property so we shouldn't use the approve status + isApproved = true; + } + } + } + else { + // if we don't use approve statuses + isApproved = true; + } + return isApproved; + } + #endregion + + #region Helper Methods + + /// + /// Checks the password. + /// + /// The password. + /// The dbPassword. + /// + internal bool CheckPassword(string password, string dbPassword) + { + string pass1 = password; + string pass2 = dbPassword; + + switch (PasswordFormat) + { + case MembershipPasswordFormat.Encrypted: + pass2 = DecodePassword(dbPassword); + break; + case MembershipPasswordFormat.Hashed: + pass1 = EncryptOrHashExistingPassword(password); + break; + default: + break; + } + return (pass1 == pass2) ? true : false; + } + + + /// + /// Encodes the password. + /// + /// The password. + /// The encoded password. + [Obsolete("Do not use this, it is the legacy way to encode a password")] + public string EncodePassword(string password) + { + return LegacyEncodePassword(password); + } + + /// + /// Unencode password. + /// + /// The encoded password. + /// The unencoded password. + [Obsolete("Do not use this, it is the legacy way to decode a password")] + public string UnEncodePassword(string encodedPassword) + { + return LegacyUnEncodePassword(encodedPassword); + } + + /// + /// Converts to membership user. + /// + /// The m. + /// + private MembershipUser ConvertToMembershipUser(Member m) + { + if (m == null) return null; + + var lastLogin = DateTime.Now; + var lastLocked = DateTime.MinValue; + var isApproved = true; + var isLocked = false; + var comment = ""; + var passwordQuestion = ""; + + // last login + if (string.IsNullOrEmpty(LastLoginPropertyTypeAlias) == false) + { + DateTime.TryParse(GetMemberProperty(m, LastLoginPropertyTypeAlias, false), out lastLogin); + } + // approved + if (string.IsNullOrEmpty(ApprovedPropertyTypeAlias) == false) + { + bool.TryParse(GetMemberProperty(m, ApprovedPropertyTypeAlias, true), out isApproved); + } + // locked + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) + { + bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLocked); + } + // last locked + if (string.IsNullOrEmpty(LastLockedOutPropertyTypeAlias) == false) + { + DateTime.TryParse(GetMemberProperty(m, LastLockedOutPropertyTypeAlias, false), out lastLocked); + } + // comment + if (string.IsNullOrEmpty(CommentPropertyTypeAlias) == false) + { + comment = GetMemberProperty(m, CommentPropertyTypeAlias, false); + } + // password question + if (string.IsNullOrEmpty(PasswordRetrievalQuestionPropertyTypeAlias) == false) + { + passwordQuestion = GetMemberProperty(m, PasswordRetrievalQuestionPropertyTypeAlias, false); + } + + return new MembershipUser(_providerName, m.LoginName, m.Id, m.Email, passwordQuestion, comment, isApproved, isLocked, m.CreateDateTime, lastLogin, + DateTime.Now, DateTime.Now, lastLocked); + } + + /// + /// Converts to membership user. + /// + /// The m. + /// + private MembershipUser ConvertToMembershipUser(IMember m) + { + if (m == null) return null; + + var lastLogin = DateTime.Now; + var lastLocked = DateTime.MinValue; + var isApproved = true; + var isLocked = false; + var comment = ""; + var passwordQuestion = ""; + + // last login + if (string.IsNullOrEmpty(LastLoginPropertyTypeAlias) == false) + { + DateTime.TryParse(GetMemberProperty(m, LastLoginPropertyTypeAlias, false), out lastLogin); + } + // approved + if (string.IsNullOrEmpty(ApprovedPropertyTypeAlias) == false) + { + bool.TryParse(GetMemberProperty(m, ApprovedPropertyTypeAlias, true), out isApproved); + } + // locked + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) + { + bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLocked); + } + // last locked + if (string.IsNullOrEmpty(LastLockedOutPropertyTypeAlias) == false) + { + DateTime.TryParse(GetMemberProperty(m, LastLockedOutPropertyTypeAlias, false), out lastLocked); + } + // comment + if (string.IsNullOrEmpty(CommentPropertyTypeAlias) == false) + { + comment = GetMemberProperty(m, CommentPropertyTypeAlias, false); + } + // password question + if (string.IsNullOrEmpty(PasswordRetrievalQuestionPropertyTypeAlias) == false) + { + passwordQuestion = GetMemberProperty(m, PasswordRetrievalQuestionPropertyTypeAlias, false); + } + + return new MembershipUser(_providerName, m.Username, m.Id, m.Email, passwordQuestion, comment, isApproved, isLocked, m.CreateDate, lastLogin, + DateTime.Now, DateTime.Now, lastLocked); + } + + #endregion + } +} diff --git a/src/umbraco.providers/members/MembersProfileProvider.cs b/src/umbraco.providers/members/UmbracoProfileProvider.cs similarity index 69% rename from src/umbraco.providers/members/MembersProfileProvider.cs rename to src/umbraco.providers/members/UmbracoProfileProvider.cs index bd8f6a5ce7..4d1ac06ea3 100644 --- a/src/umbraco.providers/members/MembersProfileProvider.cs +++ b/src/umbraco.providers/members/UmbracoProfileProvider.cs @@ -1,159 +1,187 @@ -#region namespace -using System; -using System.Collections.Generic; -using System.Text; -using System.Web.Security; -using System.Configuration; -using umbraco.BusinessLogic; -using System.Security.Cryptography; -using System.Web.Util; -using System.Collections.Specialized; -using System.Configuration.Provider; -using umbraco.cms.businesslogic; -using umbraco.cms.businesslogic.member; -using System.Collections; -using System.Web.Profile; -#endregion - -namespace umbraco.providers.members { - public class UmbracoProfileProvider : ProfileProvider { - - private string m_ApplicationName = ""; - - public override string ApplicationName { - get { - return m_ApplicationName; - } - set { - m_ApplicationName = value; - } - } - public override string Description { - get { - return "Profile Provider for umbraco member profile data"; - } - } - public override string Name { - get { - return base.Name; - } - } - public override void Initialize(string name, NameValueCollection config) { - - if (config == null) - throw new ArgumentNullException("Null configuration parameters"); - - if (String.IsNullOrEmpty(name)) - name = "UmbracoProfileProvider"; - - base.Initialize(name, config); - - m_ApplicationName = config["applicationName"]; - if (String.IsNullOrEmpty(m_ApplicationName)) - m_ApplicationName = System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath; - config.Remove("applicationName"); - - // if the config element contains unused parameters we should throw an exception - if (config.Count > 0) { - string attrib = config.GetKey(0); - if (!String.IsNullOrEmpty(attrib)) - throw new ProviderException(String.Format("Unrecognized attribute: {0}", attrib)); - } - - - } - - public override int DeleteInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate) { - throw new NotSupportedException(); - } - - public override int DeleteProfiles(string[] usernames) { - throw new NotSupportedException(); - } - - public override int DeleteProfiles(ProfileInfoCollection profiles) { - throw new NotSupportedException(); - } - - public override ProfileInfoCollection FindInactiveProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords) { - throw new NotSupportedException(); - } - - public override ProfileInfoCollection FindProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { - throw new NotSupportedException(); - } - - public override ProfileInfoCollection GetAllInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords) { - throw new NotSupportedException(); - } - public override ProfileInfoCollection GetAllProfiles(ProfileAuthenticationOption authenticationOption, int pageIndex, int pageSize, out int totalRecords) { - throw new NotSupportedException(); - } - public override int GetNumberOfInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate) { - throw new NotSupportedException(); - } - /// - /// Returns the collection of settings property values for the current umbraco member. - /// - /// A describing the current application use. - /// A containing the settings property group whose values are to be retrieved. - /// - /// A containing the values for the specified settings property group. - /// - public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection) { - SettingsPropertyValueCollection settings = new SettingsPropertyValueCollection(); - - if (collection.Count == 0) - return settings; - - foreach(SettingsProperty property in collection){ - SettingsPropertyValue pv = new SettingsPropertyValue(property); - settings.Add(pv); - } - - // get the current user - string username = (string)context["UserName"]; - Member m = Member.GetMemberFromLoginName(username); - if (m == null) - throw new ProviderException(String.Format("No member with username '{0}' exists", username)); - - foreach (SettingsPropertyValue spv in settings) { - if (m.getProperty(spv.Name) != null) { - spv.Deserialized = true; - spv.PropertyValue = m.getProperty(spv.Name).Value; - } - } - - return settings; - - } - - /// - /// Sets the values of the specified group of property settings for the current umbraco member. - /// - /// A describing the current application usage. - /// A representing the group of property settings to set. - public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection) { - - string username = (string)context["UserName"]; - bool authenticated = (bool)context["IsAuthenticated"]; - - if (String.IsNullOrEmpty(username) || collection.Count == 0) - return; - - Member m = Member.GetMemberFromLoginName(username); - if (m == null) - throw new ProviderException(String.Format("No member with username '{0}' exists", username)); - - - foreach (SettingsPropertyValue spv in collection) { - if (!authenticated && !(bool)spv.Property.Attributes["AllowAnonymous"]) - continue; - - if (m.getProperty(spv.Name) != null) - m.getProperty(spv.Name).Value = spv.PropertyValue; - } - m.Save(); - } - } -} +#region namespace +using System; +using System.Collections.Generic; +using System.Text; +using System.Web.Security; +using System.Configuration; +using umbraco.BusinessLogic; +using System.Security.Cryptography; +using System.Web.Util; +using System.Collections.Specialized; +using System.Configuration.Provider; +using umbraco.cms.businesslogic; +using umbraco.cms.businesslogic.member; +using System.Collections; +using System.Web.Profile; +#endregion + +namespace umbraco.providers.members +{ + public class UmbracoProfileProvider : ProfileProvider + { + + private string _applicationName = ""; + + public override string ApplicationName + { + get + { + return _applicationName; + } + set + { + _applicationName = value; + } + } + public override string Description + { + get + { + return "Profile Provider for umbraco member profile data"; + } + } + public override string Name + { + get + { + return base.Name; + } + } + public override void Initialize(string name, NameValueCollection config) + { + + if (config == null) + throw new ArgumentNullException("Null configuration parameters"); + + if (String.IsNullOrEmpty(name)) + name = "UmbracoProfileProvider"; + + base.Initialize(name, config); + + _applicationName = config["applicationName"]; + if (String.IsNullOrEmpty(_applicationName)) + _applicationName = System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath; + config.Remove("applicationName"); + + // if the config element contains unused parameters we should throw an exception + if (config.Count > 0) + { + var attrib = config.GetKey(0); + if (string.IsNullOrEmpty(attrib) == false) + { + throw new ProviderException(String.Format("Unrecognized attribute: {0}", attrib)); + } + + } + + + } + + public override int DeleteInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate) + { + throw new NotSupportedException(); + } + + public override int DeleteProfiles(string[] usernames) + { + throw new NotSupportedException(); + } + + public override int DeleteProfiles(ProfileInfoCollection profiles) + { + throw new NotSupportedException(); + } + + public override ProfileInfoCollection FindInactiveProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords) + { + throw new NotSupportedException(); + } + + public override ProfileInfoCollection FindProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) + { + throw new NotSupportedException(); + } + + public override ProfileInfoCollection GetAllInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords) + { + throw new NotSupportedException(); + } + public override ProfileInfoCollection GetAllProfiles(ProfileAuthenticationOption authenticationOption, int pageIndex, int pageSize, out int totalRecords) + { + throw new NotSupportedException(); + } + public override int GetNumberOfInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate) + { + throw new NotSupportedException(); + } + /// + /// Returns the collection of settings property values for the current umbraco member. + /// + /// A describing the current application use. + /// A containing the settings property group whose values are to be retrieved. + /// + /// A containing the values for the specified settings property group. + /// + public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection) + { + var settings = new SettingsPropertyValueCollection(); + + if (collection.Count == 0) + return settings; + + foreach (SettingsProperty property in collection) + { + var pv = new SettingsPropertyValue(property); + settings.Add(pv); + } + + // get the current user + var username = (string)context["UserName"]; + var m = Member.GetMemberFromLoginName(username); + if (m == null) + throw new ProviderException(String.Format("No member with username '{0}' exists", username)); + + foreach (SettingsPropertyValue spv in settings) + { + if (m.getProperty(spv.Name) != null) + { + spv.Deserialized = true; + spv.PropertyValue = m.getProperty(spv.Name).Value; + } + } + + return settings; + + } + + /// + /// Sets the values of the specified group of property settings for the current umbraco member. + /// + /// A describing the current application usage. + /// A representing the group of property settings to set. + public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection) + { + + var username = (string)context["UserName"]; + var authenticated = (bool)context["IsAuthenticated"]; + + if (string.IsNullOrEmpty(username) || collection.Count == 0) + return; + + var m = Member.GetMemberFromLoginName(username); + if (m == null) + throw new ProviderException(String.Format("No member with username '{0}' exists", username)); + + + foreach (SettingsPropertyValue spv in collection) + { + if (authenticated == false && (bool)spv.Property.Attributes["AllowAnonymous"] == false) + continue; + + if (m.getProperty(spv.Name) != null) + m.getProperty(spv.Name).Value = spv.PropertyValue; + } + m.Save(); + } + } +} diff --git a/src/umbraco.providers/members/MembersRoleProvider.cs b/src/umbraco.providers/members/UmbracoRoleProvider.cs similarity index 97% rename from src/umbraco.providers/members/MembersRoleProvider.cs rename to src/umbraco.providers/members/UmbracoRoleProvider.cs index 41a6da6568..bbae8aabfa 100644 --- a/src/umbraco.providers/members/MembersRoleProvider.cs +++ b/src/umbraco.providers/members/UmbracoRoleProvider.cs @@ -1,253 +1,256 @@ -#region namespace -using System; -using System.Collections.Generic; -using System.Text; -using System.Web.Security; -using System.Configuration; -using umbraco.BusinessLogic; -using System.Security.Cryptography; -using System.Web.Util; -using System.Collections.Specialized; -using System.Configuration.Provider; -using umbraco.cms.businesslogic; -using umbraco.cms.businesslogic.member; -using System.Collections; -#endregion - -namespace umbraco.providers.members { - public class UmbracoRoleProvider : RoleProvider { - #region - private string _ApplicationName = Member.UmbracoRoleProviderName; - #endregion - - #region Properties - /// - /// Gets or sets the name of the application to store and retrieve role information for. - /// - /// - /// The name of the application to store and retrieve role information for. - public override string ApplicationName { - get { - return _ApplicationName; - } - set { - if (string.IsNullOrEmpty(value)) - throw new ProviderException("ApplicationName cannot be empty."); - - if (value.Length > 0x100) - throw new ProviderException("Provider application name too long."); - - _ApplicationName = value; - } - } - #endregion - - #region Initialization Method - /// - /// Initializes the provider. - /// - /// The friendly name of the provider. - /// A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider. - /// The name of the provider is null. - /// An attempt is made to call - /// on a provider - /// after the provider has already been initialized. - /// The name of the provider has a length of zero. - public override void Initialize(string name, NameValueCollection config) { - // Initialize values from web.config - if (config == null) throw new ArgumentNullException("config"); - - if (name == null || name.Length == 0) name = "UmbracoMemberRoleProvider"; - - if (String.IsNullOrEmpty(config["description"])) { - config.Remove("description"); - config.Add("description", "Umbraco Member Role provider"); - } - - // Initialize the abstract base class. - base.Initialize(name, config); - - this._ApplicationName = config["applicationName"]; - if (string.IsNullOrEmpty(this._ApplicationName)) - this._ApplicationName = SecUtility.GetDefaultAppName(); - } - #endregion - - #region Methods - /// - /// Adds the specified user names to the specified roles for the configured applicationName. - /// - /// A string array of user names to be added to the specified roles. - /// A string array of the role names to add the specified user names to. - public override void AddUsersToRoles(string[] usernames, string[] roleNames) { - ArrayList roles = new ArrayList(); - foreach (string role in roleNames) - try { - roles.Add(MemberGroup.GetByName(role).Id); - } catch { - throw new ProviderException(String.Format("No role with name '{0}' exists", role)); - } - foreach (string username in usernames) { - Member m = Member.GetMemberFromLoginName(username); - foreach (int roleId in roles) - m.AddGroup(roleId); - } - } - - /// - /// Adds a new role to the data source for the configured applicationName. - /// - /// The name of the role to create. - public override void CreateRole(string roleName) { - MemberGroup.MakeNew(roleName, User.GetUser(0)); - } - - /// - /// Removes a role from the data source for the configured applicationName. - /// - /// The name of the role to delete. - /// If true, throw an exception if roleName has one or more members and do not delete roleName. - /// - /// true if the role was successfully deleted; otherwise, false. - /// - public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) { - MemberGroup group = MemberGroup.GetByName(roleName); - if (group == null) - throw new ProviderException(String.Format("No role with name '{0}' exists", roleName)); - else if (throwOnPopulatedRole && group.GetMembersAsIds().Length > 0) - throw new ProviderException(String.Format("Can't delete role '{0}', there are members assigned to the role", roleName)); - else { - foreach (Member m in group.GetMembers()) - m.RemoveGroup(group.Id); - group.delete(); - return true; - } - } - - /// - /// Gets an array of user names in a role where the user name contains the specified user name to match. - /// - /// The role to search in. - /// The user name to search for. - /// - /// A string array containing the names of all the users where the user name matches usernameToMatch and the user is a member of the specified role. - /// - public override string[] FindUsersInRole(string roleName, string usernameToMatch) { - ArrayList members = new ArrayList(); - MemberGroup group = MemberGroup.GetByName(roleName); - if (group == null) - throw new ProviderException(String.Format("No role with name '{0}' exists", roleName)); - else { - foreach (Member m in group.GetMembers(usernameToMatch)) - members.Add(m.LoginName); - return (string[])members.ToArray(typeof(string)); - } - } - - /// - /// Gets a list of all the roles for the configured applicationName. - /// - /// - /// A string array containing the names of all the roles stored in the data source for the configured applicationName. - /// - public override string[] GetAllRoles() { - ArrayList roles = new ArrayList(); - foreach (MemberGroup mg in MemberGroup.GetAll) - roles.Add(mg.Text); - return (string[])roles.ToArray(typeof(string)); - } - - /// - /// Gets a list of the roles that a specified user is in for the configured applicationName. - /// - /// The user to return a list of roles for. - /// - /// A string array containing the names of all the roles that the specified user is in for the configured applicationName. - /// - public override string[] GetRolesForUser(string username) { - ArrayList roles = new ArrayList(); - Member m = Member.GetMemberFromLoginName(username); - if (m != null) { - IDictionaryEnumerator ide = m.Groups.GetEnumerator(); - while (ide.MoveNext()) - roles.Add(((MemberGroup)ide.Value).Text); - return (string[])roles.ToArray(typeof(string)); - } else - throw new ProviderException(String.Format("No member with username '{0}' exists", username)); - } - - /// - /// Gets a list of users in the specified role for the configured applicationName. - /// - /// The name of the role to get the list of users for. - /// - /// A string array containing the names of all the users who are members of the specified role for the configured applicationName. - /// - public override string[] GetUsersInRole(string roleName) { - ArrayList members = new ArrayList(); - MemberGroup group = MemberGroup.GetByName(roleName); - if (group == null) - throw new ProviderException(String.Format("No role with name '{0}' exists", roleName)); - else { - foreach (Member m in group.GetMembers()) - members.Add(m.LoginName); - return (string[])members.ToArray(typeof(string)); - } - } - - /// - /// Gets a value indicating whether the specified user is in the specified role for the configured applicationName. - /// - /// The user name to search for. - /// The role to search in. - /// - /// true if the specified user is in the specified role for the configured applicationName; otherwise, false. - /// - public override bool IsUserInRole(string username, string roleName) { - Member m = Member.GetMemberFromLoginName(username); - if (m == null) - throw new ProviderException(String.Format("No user with name '{0}' exists", username)); - else { - MemberGroup mg = MemberGroup.GetByName(roleName); - if (mg == null) - throw new ProviderException(String.Format("No Membergroup with name '{0}' exists", roleName)); - else - return mg.HasMember(m.Id); - } - } - - /// - /// Removes the specified user names from the specified roles for the configured applicationName. - /// - /// A string array of user names to be removed from the specified roles. - /// A string array of role names to remove the specified user names from. - public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames) { - ArrayList roles = new ArrayList(); - foreach (string role in roleNames) - try { - roles.Add(MemberGroup.GetByName(role).Id); - } catch { - throw new ProviderException(String.Format("No role with name '{0}' exists", role)); - } - foreach (string username in usernames) { - Member m = Member.GetMemberFromLoginName(username); - foreach (int roleId in roles) - m.RemoveGroup(roleId); - } - } - - /// - /// Gets a value indicating whether the specified role name already exists in the role data source for the configured applicationName. - /// - /// The name of the role to search for in the data source. - /// - /// true if the role name already exists in the data source for the configured applicationName; otherwise, false. - /// - public override bool RoleExists(string roleName) { - MemberGroup mg = MemberGroup.GetByName(roleName); - return mg != null; - } - #endregion - - } -} +#region namespace +using System; +using System.Collections.Generic; +using System.Text; +using System.Web.Security; +using System.Configuration; +using umbraco.BusinessLogic; +using System.Security.Cryptography; +using System.Web.Util; +using System.Collections.Specialized; +using System.Configuration.Provider; +using umbraco.cms.businesslogic; +using umbraco.cms.businesslogic.member; +using System.Collections; +#endregion + +namespace umbraco.providers.members +{ + public class UmbracoRoleProvider : RoleProvider + { + + #region + private string _ApplicationName = Member.UmbracoRoleProviderName; + #endregion + + #region Properties + /// + /// Gets or sets the name of the application to store and retrieve role information for. + /// + /// + /// The name of the application to store and retrieve role information for. + public override string ApplicationName { + get { + return _ApplicationName; + } + set { + if (string.IsNullOrEmpty(value)) + throw new ProviderException("ApplicationName cannot be empty."); + + if (value.Length > 0x100) + throw new ProviderException("Provider application name too long."); + + _ApplicationName = value; + } + } + #endregion + + #region Initialization Method + /// + /// Initializes the provider. + /// + /// The friendly name of the provider. + /// A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider. + /// The name of the provider is null. + /// An attempt is made to call + /// on a provider + /// after the provider has already been initialized. + /// The name of the provider has a length of zero. + public override void Initialize(string name, NameValueCollection config) { + // Initialize values from web.config + if (config == null) throw new ArgumentNullException("config"); + + if (name == null || name.Length == 0) name = "UmbracoMemberRoleProvider"; + + if (String.IsNullOrEmpty(config["description"])) { + config.Remove("description"); + config.Add("description", "Umbraco Member Role provider"); + } + + // Initialize the abstract base class. + base.Initialize(name, config); + + this._ApplicationName = config["applicationName"]; + if (string.IsNullOrEmpty(this._ApplicationName)) + this._ApplicationName = SecUtility.GetDefaultAppName(); + } + #endregion + + #region Methods + /// + /// Adds the specified user names to the specified roles for the configured applicationName. + /// + /// A string array of user names to be added to the specified roles. + /// A string array of the role names to add the specified user names to. + public override void AddUsersToRoles(string[] usernames, string[] roleNames) { + ArrayList roles = new ArrayList(); + foreach (string role in roleNames) + try { + roles.Add(MemberGroup.GetByName(role).Id); + } catch { + throw new ProviderException(String.Format("No role with name '{0}' exists", role)); + } + foreach (string username in usernames) { + Member m = Member.GetMemberFromLoginName(username); + foreach (int roleId in roles) + m.AddGroup(roleId); + } + } + + /// + /// Adds a new role to the data source for the configured applicationName. + /// + /// The name of the role to create. + public override void CreateRole(string roleName) { + MemberGroup.MakeNew(roleName, User.GetUser(0)); + } + + /// + /// Removes a role from the data source for the configured applicationName. + /// + /// The name of the role to delete. + /// If true, throw an exception if roleName has one or more members and do not delete roleName. + /// + /// true if the role was successfully deleted; otherwise, false. + /// + public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) { + MemberGroup group = MemberGroup.GetByName(roleName); + if (group == null) + throw new ProviderException(String.Format("No role with name '{0}' exists", roleName)); + else if (throwOnPopulatedRole && group.GetMembersAsIds().Length > 0) + throw new ProviderException(String.Format("Can't delete role '{0}', there are members assigned to the role", roleName)); + else { + foreach (Member m in group.GetMembers()) + m.RemoveGroup(group.Id); + group.delete(); + return true; + } + } + + /// + /// Gets an array of user names in a role where the user name contains the specified user name to match. + /// + /// The role to search in. + /// The user name to search for. + /// + /// A string array containing the names of all the users where the user name matches usernameToMatch and the user is a member of the specified role. + /// + public override string[] FindUsersInRole(string roleName, string usernameToMatch) { + ArrayList members = new ArrayList(); + MemberGroup group = MemberGroup.GetByName(roleName); + if (group == null) + throw new ProviderException(String.Format("No role with name '{0}' exists", roleName)); + else { + foreach (Member m in group.GetMembers(usernameToMatch)) + members.Add(m.LoginName); + return (string[])members.ToArray(typeof(string)); + } + } + + /// + /// Gets a list of all the roles for the configured applicationName. + /// + /// + /// A string array containing the names of all the roles stored in the data source for the configured applicationName. + /// + public override string[] GetAllRoles() { + ArrayList roles = new ArrayList(); + foreach (MemberGroup mg in MemberGroup.GetAll) + roles.Add(mg.Text); + return (string[])roles.ToArray(typeof(string)); + } + + /// + /// Gets a list of the roles that a specified user is in for the configured applicationName. + /// + /// The user to return a list of roles for. + /// + /// A string array containing the names of all the roles that the specified user is in for the configured applicationName. + /// + public override string[] GetRolesForUser(string username) { + ArrayList roles = new ArrayList(); + Member m = Member.GetMemberFromLoginName(username); + if (m != null) { + IDictionaryEnumerator ide = m.Groups.GetEnumerator(); + while (ide.MoveNext()) + roles.Add(((MemberGroup)ide.Value).Text); + return (string[])roles.ToArray(typeof(string)); + } else + throw new ProviderException(String.Format("No member with username '{0}' exists", username)); + } + + /// + /// Gets a list of users in the specified role for the configured applicationName. + /// + /// The name of the role to get the list of users for. + /// + /// A string array containing the names of all the users who are members of the specified role for the configured applicationName. + /// + public override string[] GetUsersInRole(string roleName) { + ArrayList members = new ArrayList(); + MemberGroup group = MemberGroup.GetByName(roleName); + if (group == null) + throw new ProviderException(String.Format("No role with name '{0}' exists", roleName)); + else { + foreach (Member m in group.GetMembers()) + members.Add(m.LoginName); + return (string[])members.ToArray(typeof(string)); + } + } + + /// + /// Gets a value indicating whether the specified user is in the specified role for the configured applicationName. + /// + /// The user name to search for. + /// The role to search in. + /// + /// true if the specified user is in the specified role for the configured applicationName; otherwise, false. + /// + public override bool IsUserInRole(string username, string roleName) { + Member m = Member.GetMemberFromLoginName(username); + if (m == null) + throw new ProviderException(String.Format("No user with name '{0}' exists", username)); + else { + MemberGroup mg = MemberGroup.GetByName(roleName); + if (mg == null) + throw new ProviderException(String.Format("No Membergroup with name '{0}' exists", roleName)); + else + return mg.HasMember(m.Id); + } + } + + /// + /// Removes the specified user names from the specified roles for the configured applicationName. + /// + /// A string array of user names to be removed from the specified roles. + /// A string array of role names to remove the specified user names from. + public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames) { + ArrayList roles = new ArrayList(); + foreach (string role in roleNames) + try { + roles.Add(MemberGroup.GetByName(role).Id); + } catch { + throw new ProviderException(String.Format("No role with name '{0}' exists", role)); + } + foreach (string username in usernames) { + Member m = Member.GetMemberFromLoginName(username); + foreach (int roleId in roles) + m.RemoveGroup(roleId); + } + } + + /// + /// Gets a value indicating whether the specified role name already exists in the role data source for the configured applicationName. + /// + /// The name of the role to search for in the data source. + /// + /// true if the role name already exists in the data source for the configured applicationName; otherwise, false. + /// + public override bool RoleExists(string roleName) { + MemberGroup mg = MemberGroup.GetByName(roleName); + return mg != null; + } + #endregion + + } +} diff --git a/src/umbraco.providers/umbraco.providers.csproj b/src/umbraco.providers/umbraco.providers.csproj index de33276993..e7e5e28b12 100644 --- a/src/umbraco.providers/umbraco.providers.csproj +++ b/src/umbraco.providers/umbraco.providers.csproj @@ -80,9 +80,9 @@ - - - + + + From 414b65b269d230fe67ac7e5df44d852510b56aa5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 20 Dec 2013 14:01:10 +1100 Subject: [PATCH 15/27] More WIP on the membership providers - both new and old. They are now both working with the same up-to-date logic and base classes. Updated the interface definitions on the member services to support the membership provider queries - now to implement the logic. --- .../Querying/ValuePropertyMatchType.cs | 14 + .../Security/MembershipProviderBase.cs | 108 +++-- src/Umbraco.Core/Services/IMemberService.cs | 73 ++- .../Services/IMembershipMemberService.cs | 20 + src/Umbraco.Core/Services/MemberCountType.cs | 13 + src/Umbraco.Core/Services/MemberService.cs | 26 +- src/Umbraco.Core/Umbraco.Core.csproj | 2 + .../Membership/MembershipProviderBaseTests.cs | 30 +- .../Providers/MembersMembershipProvider.cs | 449 +++++++++--------- .../UsersMembershipProvider.cs | 28 +- .../members/UmbracoMembershipProvider.cs | 370 ++++++++------- 11 files changed, 668 insertions(+), 465 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs create mode 100644 src/Umbraco.Core/Services/MemberCountType.cs diff --git a/src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs b/src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs new file mode 100644 index 0000000000..f0c70a069d --- /dev/null +++ b/src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Core.Persistence.Querying +{ + /// + /// Determine how to match a number or data value + /// + public enum ValuePropertyMatchType + { + Exact, + GreaterThan, + LessThan, + GreaterThanOrEqualTo, + LessThanOrEqualTo + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs index 24a0e29409..9d76c8e595 100644 --- a/src/Umbraco.Core/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs @@ -18,40 +18,10 @@ namespace Umbraco.Core.Security { //Set the defaults! DefaultMemberTypeAlias = "Member"; - LockPropertyTypeAlias = Constants.Conventions.Member.IsLockedOut; - LastLockedOutPropertyTypeAlias = Constants.Conventions.Member.LastLockoutDate; - FailedPasswordAttemptsPropertyTypeAlias = Constants.Conventions.Member.FailedPasswordAttempts; - ApprovedPropertyTypeAlias = Constants.Conventions.Member.IsApproved; - CommentPropertyTypeAlias = Constants.Conventions.Member.Comments; - LastLoginPropertyTypeAlias = Constants.Conventions.Member.LastLoginDate; - LastPasswordChangedPropertyTypeAlias = Constants.Conventions.Member.LastPasswordChangeDate; - PasswordRetrievalQuestionPropertyTypeAlias = Constants.Conventions.Member.PasswordQuestion; - PasswordRetrievalAnswerPropertyTypeAlias = Constants.Conventions.Member.PasswordAnswer; } public string DefaultMemberTypeAlias { get; protected set; } - public string LockPropertyTypeAlias { get; protected set; } - public string LastLockedOutPropertyTypeAlias { get; protected set; } - public string FailedPasswordAttemptsPropertyTypeAlias { get; protected set; } - public string ApprovedPropertyTypeAlias { get; protected set; } - public string CommentPropertyTypeAlias { get; protected set; } - public string LastLoginPropertyTypeAlias { get; protected set; } - public string LastPasswordChangedPropertyTypeAlias { get; protected set; } - public string PasswordRetrievalQuestionPropertyTypeAlias { get; protected set; } - public string PasswordRetrievalAnswerPropertyTypeAlias { get; protected set; } - - public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) - { - base.ChangePasswordQuestionAndAnswer(username, password, newPasswordQuestion, newPasswordAnswer); - - if (string.IsNullOrEmpty(PasswordRetrievalQuestionPropertyTypeAlias) || string.IsNullOrEmpty(PasswordRetrievalAnswerPropertyTypeAlias)) - { - throw new NotSupportedException("Updating the password Question and Answer is not valid if the properties aren't set in the config file"); - } - - return PerformChangePasswordQuestionAndAnswer(username, password, newPasswordQuestion, newPasswordAnswer); - } - + /// /// Adds a new membership user to the data source. /// @@ -398,6 +368,21 @@ namespace Umbraco.Core.Security throw new NotSupportedException("This provider does not support manually changing the password"); } + var args = new ValidatePasswordEventArgs(username, newPassword, false); + OnValidatingPassword(args); + + if (args.Cancel) + { + if (args.FailureInformation != null) + throw args.FailureInformation; + throw new MembershipPasswordException("Change password canceled due to password validation failure."); + } + + if (AllowManuallyChangingPassword == false) + { + if (ValidateUser(username, oldPassword) == false) return false; + } + return PerformChangePassword(username, oldPassword, newPassword); } @@ -431,10 +416,13 @@ namespace Umbraco.Core.Security { throw new NotSupportedException("Updating the password Question and Answer is not available if requiresQuestionAndAnswer is not set in web.config"); } - - if (ValidateUser(username, password) == false) + + if (AllowManuallyChangingPassword == false) { - return false; + if (ValidateUser(username, password) == false) + { + return false; + } } return PerformChangePasswordQuestionAndAnswer(username, password, newPasswordQuestion, newPasswordAnswer); @@ -534,6 +522,56 @@ namespace Umbraco.Core.Security /// protected abstract MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status); + /// + /// Gets the members password if password retreival is enabled + /// + /// + /// + /// + public sealed override string GetPassword(string username, string answer) + { + if (EnablePasswordRetrieval == false) + throw new ProviderException("Password Retrieval Not Enabled."); + + if (PasswordFormat == MembershipPasswordFormat.Hashed) + throw new ProviderException("Cannot retrieve Hashed passwords."); + + return PerformGetPassword(username, answer); + } + + /// + /// Gets the members password if password retreival is enabled + /// + /// + /// + /// + protected abstract string PerformGetPassword(string username, string answer); + + public sealed override string ResetPassword(string username, string answer) + { + if (EnablePasswordReset == false) + { + throw new NotSupportedException("Password reset is not supported"); + } + + var newPassword = Membership.GeneratePassword(MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters); + + var args = new ValidatePasswordEventArgs(username, newPassword, true); + OnValidatingPassword(args); + if (args.Cancel) + { + if (args.FailureInformation != null) + { + throw args.FailureInformation; + } + throw new MembershipPasswordException("Reset password canceled due to password validation failure."); + } + + return PerformResetPassword(username, answer, newPassword); + } + + protected abstract string PerformResetPassword(string username, string answer, string generatedPassword); + protected internal static Attempt IsPasswordValid(string password, int minRequiredNonAlphanumericChars, string strengthRegex, int minLength) { if (minRequiredNonAlphanumericChars > 0) @@ -561,7 +599,7 @@ namespace Umbraco.Core.Security return Attempt.Succeed(PasswordValidityError.Ok); } - + /// /// Gets the name of the default app. /// diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 0be5211532..87c15573c6 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -17,18 +17,87 @@ namespace Umbraco.Core.Services /// bool Exists(int id); + /// + /// Get a member by its id + /// + /// + /// IMember GetById(int id); + + /// + /// Get a member by the unique key + /// + /// + /// IMember GetByKey(Guid id); + + /// + /// Get all members for the member type alias + /// + /// + /// IEnumerable GetMembersByMemberType(string memberTypeAlias); + + /// + /// Get all members for the member type id + /// + /// + /// IEnumerable GetMembersByMemberType(int memberTypeId); + + /// + /// Get all members in the member group name specified + /// + /// + /// IEnumerable GetMembersByGroup(string memberGroupName); + + /// + /// Get all members with the ids specified + /// + /// + /// IEnumerable GetAllMembers(params int[] ids); + /// + /// Delete members of the specified member type id + /// + /// void DeleteMembersOfType(int memberTypeId); + /// + /// Get members based on a property search + /// + /// + /// + /// + /// IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact); - IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value); + + /// + /// Get members based on a property search + /// + /// + /// + /// + /// + IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact); + + /// + /// Get members based on a property search + /// + /// + /// + /// IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, bool value); - IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value); + + /// + /// Get members based on a property search + /// + /// + /// + /// + /// + IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IMembershipMemberService.cs b/src/Umbraco.Core/Services/IMembershipMemberService.cs index 18edae2bea..715f85f3fb 100644 --- a/src/Umbraco.Core/Services/IMembershipMemberService.cs +++ b/src/Umbraco.Core/Services/IMembershipMemberService.cs @@ -31,6 +31,11 @@ namespace Umbraco.Core.Services IMember GetById(object id); + /// + /// Get a member by email + /// + /// + /// IMember GetByEmail(string email); IMember GetByUsername(string login); @@ -44,5 +49,20 @@ namespace Umbraco.Core.Services IEnumerable FindMembersByEmail(string emailStringToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); IEnumerable FindMembersByUsername(string login, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + + /// + /// Gets the total number of members based on the count type + /// + /// + int GetMemberCount(MemberCountType countType); + + /// + /// Gets a list of paged member data + /// + /// + /// + /// + /// + IEnumerable GetAllMembers(int pageIndex, int pageSize, out int totalRecords); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MemberCountType.cs b/src/Umbraco.Core/Services/MemberCountType.cs new file mode 100644 index 0000000000..882d1515e2 --- /dev/null +++ b/src/Umbraco.Core/Services/MemberCountType.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Core.Services +{ + /// + /// The types of members to count + /// + public enum MemberCountType + { + All, + Online, + LockedOut, + Approved + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index a84734d1f7..a5333cc503 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -310,8 +310,9 @@ namespace Umbraco.Core.Services /// /// /// + /// /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value) + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) { using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) { @@ -352,8 +353,9 @@ namespace Umbraco.Core.Services /// /// /// + /// /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value) + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) { using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) { @@ -372,6 +374,26 @@ namespace Umbraco.Core.Services #region IMembershipMemberService Implementation + /// + /// Returns the count of members based on the countType + /// + /// + /// + /// + /// The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members + /// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science + /// but that is how MS have made theirs so we'll follow that principal. + /// + public int GetMemberCount(MemberCountType countType) + { + throw new NotImplementedException(); + } + + public IEnumerable GetAllMembers(int pageIndex, int pageSize, out int totalRecords) + { + throw new NotImplementedException(); + } + /// /// Creates and persists a new Member /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 02b36c8e85..9f7bd325a1 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -203,6 +203,7 @@ + @@ -772,6 +773,7 @@ + diff --git a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs index 06e44be215..f1b8d66773 100644 --- a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs +++ b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs @@ -13,6 +13,16 @@ using Umbraco.Web.Security.Providers; namespace Umbraco.Tests.Membership { + [TestFixture] + public class MembersMembershipProviderTests + { + //[Test] + //public void Set_Default_Member_Type_On_Init() + + //[Test] + //public void Question_Answer_Is_Encrypted() + } + [TestFixture] public class MembershipProviderBaseTests { @@ -111,16 +121,6 @@ namespace Umbraco.Tests.Membership private class TestProvider : MembershipProviderBase { - public override string GetPassword(string username, string answer) - { - throw new NotImplementedException(); - } - - public override string ResetPassword(string username, string answer) - { - throw new NotImplementedException(); - } - public override void UpdateUser(MembershipUser user) { throw new NotImplementedException(); @@ -190,6 +190,16 @@ namespace Umbraco.Tests.Membership { throw new NotImplementedException(); } + + protected override string PerformGetPassword(string username, string answer) + { + throw new NotImplementedException(); + } + + protected override string PerformResetPassword(string username, string answer, string generatedPassword) + { + throw new NotImplementedException(); + } } } diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index 23ee46fabc..133314d31a 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -61,48 +61,7 @@ namespace Umbraco.Web.Security.Providers // _defaultMemberTypeAlias = MemberType.GetAll[0].Alias; //else // throw new ProviderException("No default MemberType alias is specified in the web.config string. Please add a 'defaultMemberTypeAlias' to the add element in the provider declaration in web.config"); - - // test for approve status - if (config["umbracoApprovePropertyTypeAlias"] != null) - { - ApprovedPropertyTypeAlias = config["umbracoApprovePropertyTypeAlias"]; - } - // test for lock attempts - if (config["umbracoLockPropertyTypeAlias"] != null) - { - LockPropertyTypeAlias = config["umbracoLockPropertyTypeAlias"]; - } - if (config["umbracoLastLockedPropertyTypeAlias"] != null) - { - LastLockedOutPropertyTypeAlias = config["umbracoLastLockedPropertyTypeAlias"]; - } - if (config["umbracoLastPasswordChangedPropertyTypeAlias"] != null) - { - LastPasswordChangedPropertyTypeAlias = config["umbracoLastPasswordChangedPropertyTypeAlias"]; - } - if (config["umbracoFailedPasswordAttemptsPropertyTypeAlias"] != null) - { - FailedPasswordAttemptsPropertyTypeAlias = config["umbracoFailedPasswordAttemptsPropertyTypeAlias"]; - } - // comment property - if (config["umbracoCommentPropertyTypeAlias"] != null) - { - CommentPropertyTypeAlias = config["umbracoCommentPropertyTypeAlias"]; - } - // last login date - if (config["umbracoLastLoginPropertyTypeAlias"] != null) - { - LastLoginPropertyTypeAlias = config["umbracoLastLoginPropertyTypeAlias"]; - } - // password retrieval - if (config["umbracoPasswordRetrievalQuestionPropertyTypeAlias"] != null) - { - PasswordRetrievalQuestionPropertyTypeAlias = config["umbracoPasswordRetrievalQuestionPropertyTypeAlias"]; - } - if (config["umbracoPasswordRetrievalAnswerPropertyTypeAlias"] != null) - { - PasswordRetrievalAnswerPropertyTypeAlias = config["umbracoPasswordRetrievalAnswerPropertyTypeAlias"]; - } + } /// @@ -123,17 +82,7 @@ namespace Umbraco.Web.Security.Providers // in order to support updating passwords from the umbraco core, we can't validate the old password var m = _memberService.GetByUsername(username); if (m == null) return false; - - var args = new ValidatePasswordEventArgs(username, newPassword, false); - OnValidatingPassword(args); - - if (args.Cancel) - { - if (args.FailureInformation != null) - throw args.FailureInformation; - throw new MembershipPasswordException("Change password canceled due to password validation failure."); - } - + string salt; var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); @@ -289,7 +238,45 @@ namespace Umbraco.Web.Security.Providers } return collection; } - + + /// + /// Gets a collection of all the users in the data source in pages of data. + /// + /// The index of the page of results to return. pageIndex is zero-based. + /// The size of the page of results to return. + /// The total number of matched users. + /// + /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. + /// + public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) + { + var membersList = new MembershipUserCollection(); + + var pagedMembers = MemberService.GetAllMembers(pageIndex, pageSize, out totalRecords); + + foreach (var m in pagedMembers) + { + membersList.Add(m.AsConcreteMembershipUser()); + } + return membersList; + } + + /// + /// Gets the number of users currently accessing the application. + /// + /// + /// The number of users currently accessing the application. + /// + /// + /// The way this is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members + /// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science + /// but that is how MS have made theirs so we'll follow that principal. + /// + public override int GetNumberOfUsersOnline() + { + return MemberService.GetMemberCount(MemberCountType.Online); + } + /// /// Gets the password for the specified user name from the data source. /// @@ -298,168 +285,24 @@ namespace Umbraco.Web.Security.Providers /// /// The password for the specified user name. /// - public override string GetPassword(string username, string answer) - { - if (EnablePasswordRetrieval == false) - throw new ProviderException("Password Retrieval Not Enabled."); - - if (PasswordFormat == MembershipPasswordFormat.Hashed) - throw new ProviderException("Cannot retrieve Hashed passwords."); - - var member = MemberService.GetByUsername(username); - - if (RequiresQuestionAndAnswer && member.PasswordAnswer != answer) + protected override string PerformGetPassword(string username, string answer) + { + var m = MemberService.GetByUsername(username); + if (m == null) { - throw new ProviderException("Password retrieval answer doesn't match"); - } - - return member.Password; - } - - /// - /// Resets a user's password to a new, automatically generated password. - /// - /// The user to reset the password for. - /// The password answer for the specified user (not used with Umbraco). - /// The new password for the specified user. - public override string ResetPassword(string username, string answer) - { - //TODO: Get logic from other provider - - throw new NotImplementedException(); - - //if (EnablePasswordReset == false) - // throw new ProviderException("Password reset is Not Enabled."); - - //var member = MemberService.GetByUsername(username); - - //if (member == null) - // throw new ProviderException("The supplied user is not found"); - - //if(member.IsLockedOut) - // throw new ProviderException("The member is locked out."); - - //if (RequiresQuestionAndAnswer == false || (RequiresQuestionAndAnswer && answer == member.PasswordAnswer)) - //{ - // member.Password = - // EncryptOrHashPassword(Membership.GeneratePassword(MinRequiredPasswordLength, - // MinRequiredNonAlphanumericCharacters)); - // MemberService.Save(member); - //} - //else - //{ - // throw new MembershipPasswordException("Incorrect password answer"); - //} - - //return null; - } - - /// - /// Updates e-mail and potentially approved status, lock status and comment on a user. - /// Note: To automatically support lock, approve and comments you'll need to add references to the membertype properties in the - /// 'Member' element in web.config by adding their aliases to the 'umbracoApprovePropertyTypeAlias', 'umbracoLockPropertyTypeAlias' and 'umbracoCommentPropertyTypeAlias' attributes - /// - /// A object that represents the user to update and the updated information for the user. - public override void UpdateUser(MembershipUser user) - { - //var member = user.AsIMember(); - //MemberService.Save(member); - } - - /// - /// Verifies that the specified user name and password exist in the data source. - /// - /// The name of the user to validate. - /// The password for the specified user. - /// - /// true if the specified username and password are valid; otherwise, false. - /// - public override bool ValidateUser(string username, string password) - { - var member = MemberService.GetByUsername(username); - - if (member == null || member.IsApproved == false) return false; - - if (member.IsLockedOut) - throw new ProviderException("The member is locked out."); - - string salt; - var encodedPassword = EncryptOrHashNewPassword(password, out salt); - - var authenticated = (encodedPassword == member.Password); - - if (authenticated == false) - { - // TODO: Increment login attempts - lock if too many. - - //var count = member.GetValue("loginAttempts"); - //count++; - - //if (count >= _maxInvalidPasswordAttempts) - //{ - // member.SetValue("loginAttempts", 0); - // member.IsLockedOut = true; - // throw new ProviderException("The member " + member.Username + " is locked out."); - //} - //else - //{ - // member.SetValue("loginAttempts", count); - //} - } - else - { - // add this later - member.SetValue("loginAttempts", 0); - member.LastLoginDate = DateTime.Now; + throw new ProviderException("The supplied user is not found"); } - MemberService.Save(member); - return authenticated; - } + //TODO: We need to encrypt the answer here to match against the encrypted answer in the database - /// - /// Clears a lock so that the membership user can be validated. - /// - /// The membership user to clear the lock status for. - /// - /// true if the membership user was successfully unlocked; otherwise, false. - /// - public override bool UnlockUser(string username) - { - var member = MemberService.GetByUsername(username); - - if(member == null) - throw new ProviderException("Cannot find member " + username); - - // Non need to update - if (member.IsLockedOut == false) return true; - - member.IsLockedOut = false; - // TODO: add this later - member.SetValue("loginAttempts", 0); - - MemberService.Save(member); - - return true; - } - - /// - /// Gets information from the data source for a user based on the unique identifier for the membership user. Provides an option to update the last-activity date/time stamp for the user. - /// - /// The unique identifier for the membership user to get information for. - /// true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user. - /// - /// A object populated with the specified user's information from the data source. - /// - public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) - { - var member = MemberService.GetById(providerUserKey); - - if (userIsOnline) + if (RequiresQuestionAndAnswer && m.PasswordAnswer != answer) { - member.UpdateDate = DateTime.Now; - MemberService.Save(member); + throw new ProviderException("Incorrect password answer"); } - return member.AsConcreteMembershipUser(); + var decodedPassword = DecodePassword(m.Password); + + return decodedPassword; } /// @@ -473,9 +316,40 @@ namespace Umbraco.Web.Security.Providers public override MembershipUser GetUser(string username, bool userIsOnline) { var member = MemberService.GetByUsername(username); + if (member == null) + { + return null; + } if (userIsOnline) { + member.LastLoginDate = DateTime.Now; + member.UpdateDate = DateTime.Now; + MemberService.Save(member); + } + + return member.AsConcreteMembershipUser(); + } + + /// + /// Gets information from the data source for a user based on the unique identifier for the membership user. Provides an option to update the last-activity date/time stamp for the user. + /// + /// The unique identifier for the membership user to get information for. + /// true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user. + /// + /// A object populated with the specified user's information from the data source. + /// + public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) + { + var member = MemberService.GetById(providerUserKey); + if (member == null) + { + return null; + } + + if (userIsOnline) + { + member.LastLoginDate = DateTime.Now; member.UpdateDate = DateTime.Now; MemberService.Save(member); } @@ -497,33 +371,158 @@ namespace Umbraco.Web.Security.Providers return member == null ? null : member.Username; } - - /// - /// Gets a collection of all the users in the data source in pages of data. + /// Resets a user's password to a new, automatically generated password. /// - /// The index of the page of results to return. pageIndex is zero-based. - /// The size of the page of results to return. - /// The total number of matched users. - /// - /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. - /// - public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) + /// The user to reset the password for. + /// The password answer for the specified user (not used with Umbraco). + /// + /// The new password for the specified user. + protected override string PerformResetPassword(string username, string answer, string generatedPassword) { - throw new System.NotImplementedException(); + //TODO: This should be here - but how do we update failure count in this provider?? + //if (answer == null && RequiresQuestionAndAnswer) + //{ + // UpdateFailureCount(username, "passwordAnswer"); + + // throw new ProviderException("Password answer required for password reset."); + //} + + var m = MemberService.GetByUsername(username); + if (m == null) + { + throw new ProviderException("The supplied user is not found"); + } + + if (m.IsLockedOut) + { + throw new ProviderException("The member is locked out."); + } + + //TODO: We need to encrypt the answer here to match against the encrypted answer in the database + + if (RequiresQuestionAndAnswer && m.PasswordAnswer != answer) + { + throw new ProviderException("Incorrect password answer"); + } + + string salt; + var encodedPassword = EncryptOrHashNewPassword(generatedPassword, out salt); + m.Password = encodedPassword; + m.LastPasswordChangeDate = DateTime.Now; + MemberService.Save(m); + + return generatedPassword; } /// - /// Gets the number of users currently accessing the application. + /// Clears a lock so that the membership user can be validated. /// + /// The membership user to clear the lock status for. /// - /// The number of users currently accessing the application. + /// true if the membership user was successfully unlocked; otherwise, false. /// - public override int GetNumberOfUsersOnline() + public override bool UnlockUser(string username) { - throw new System.NotImplementedException(); + var member = MemberService.GetByUsername(username); + + if (member == null) + { + throw new ProviderException(string.Format("No member with the username '{0}' found", username)); + } + + // Non need to update + if (member.IsLockedOut == false) return true; + + member.IsLockedOut = false; + member.FailedPasswordAttempts = 0; + + MemberService.Save(member); + + return true; } - + + /// + /// Updates e-mail approved status, lock status and comment on a user. + /// + /// A object that represents the user to update and the updated information for the user. + public override void UpdateUser(MembershipUser user) + { + var m = MemberService.GetByUsername(user.UserName); + + if (m == null) + { + throw new ProviderException(string.Format("No member with the username '{0}' found", user.UserName)); + } + + m.Email = user.Email; + m.IsApproved = user.IsApproved; + m.IsLockedOut = user.IsLockedOut; + if (user.IsLockedOut) + { + m.LastLockoutDate = DateTime.Now; + } + m.Comments = user.Comment; + + MemberService.Save(m); + } + + /// + /// Verifies that the specified user name and password exist in the data source. + /// + /// The name of the user to validate. + /// The password for the specified user. + /// + /// true if the specified username and password are valid; otherwise, false. + /// + public override bool ValidateUser(string username, string password) + { + var member = MemberService.GetByUsername(username); + + if (member == null) return false; + + if (member.IsApproved == false) + { + LogHelper.Info("Cannot validate member " + username + " because they are not approved"); + return false; + } + if (member.IsLockedOut) + { + LogHelper.Info("Cannot validate member " + username + " because they are currently locked out"); + return false; + } + + string salt; + var encodedPassword = EncryptOrHashNewPassword(password, out salt); + + var authenticated = (encodedPassword == member.Password); + + if (authenticated == false) + { + // TODO: Increment login attempts - lock if too many. + + var count = member.FailedPasswordAttempts; + count++; + member.FailedPasswordAttempts = count; + + if (count >= MaxInvalidPasswordAttempts) + { + member.IsLockedOut = true; + member.LastLockoutDate = DateTime.Now; + LogHelper.Info("Member " + username + " is now locked out, max invalid password attempts exceeded"); + } + } + else + { + member.FailedPasswordAttempts = 0; + member.LastLoginDate = DateTime.Now; + } + + MemberService.Save(member); + return authenticated; + } + + public override string ToString() { diff --git a/src/umbraco.providers/UsersMembershipProvider.cs b/src/umbraco.providers/UsersMembershipProvider.cs index e862bf5b19..64534abf3a 100644 --- a/src/umbraco.providers/UsersMembershipProvider.cs +++ b/src/umbraco.providers/UsersMembershipProvider.cs @@ -305,7 +305,7 @@ namespace umbraco.providers /// /// The password for the specified user name. /// - public override string GetPassword(string username, string answer) + protected override string PerformGetPassword(string username, string answer) { throw new ProviderException("Password Retrieval Not Enabled."); } @@ -355,13 +355,8 @@ namespace umbraco.providers /// The user to reset the password for. /// The password answer for the specified user. /// The new password for the specified user. - public override string ResetPassword(string username, string answer) - { - if (EnablePasswordReset == false) - { - throw new NotSupportedException("Password reset is not supported"); - } - + protected override string PerformResetPassword(string username, string answer, string generatedPassword) + { //TODO: This should be here - but how do we update failure count in this provider?? //if (answer == null && RequiresQuestionAndAnswer) //{ @@ -370,18 +365,7 @@ namespace umbraco.providers // throw new ProviderException("Password answer required for password reset."); //} - var newPassword = Membership.GeneratePassword(MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters); - - var args = new ValidatePasswordEventArgs(username, newPassword, true); - OnValidatingPassword(args); - if (args.Cancel) - { - if (args.FailureInformation != null) - throw args.FailureInformation; - throw new MembershipPasswordException("Reset password canceled due to password validation failure."); - } - - var found = User.GetAllByLoginName(username, false); + var found = User.GetAllByLoginName(username, false).ToArray(); if (found == null || found.Any() == false) throw new MembershipPasswordException("The supplied user is not found"); @@ -389,12 +373,12 @@ namespace umbraco.providers //Yes, it's true, this actually makes a db call to set the password string salt; - var encPass = EncryptOrHashNewPassword(newPassword, out salt); + var encPass = EncryptOrHashNewPassword(generatedPassword, out salt); user.Password = FormatPasswordForStorage(encPass, salt); //call this just for fun. user.Save(); - return newPassword; + return generatedPassword; } /// diff --git a/src/umbraco.providers/members/UmbracoMembershipProvider.cs b/src/umbraco.providers/members/UmbracoMembershipProvider.cs index 62562a85b0..500444b3fd 100644 --- a/src/umbraco.providers/members/UmbracoMembershipProvider.cs +++ b/src/umbraco.providers/members/UmbracoMembershipProvider.cs @@ -31,12 +31,35 @@ namespace umbraco.providers.members public class UmbracoMembershipProvider : UmbracoMembershipProviderBase { + public UmbracoMembershipProvider() + { + LockPropertyTypeAlias = Constants.Conventions.Member.IsLockedOut; + LastLockedOutPropertyTypeAlias = Constants.Conventions.Member.LastLockoutDate; + FailedPasswordAttemptsPropertyTypeAlias = Constants.Conventions.Member.FailedPasswordAttempts; + ApprovedPropertyTypeAlias = Constants.Conventions.Member.IsApproved; + CommentPropertyTypeAlias = Constants.Conventions.Member.Comments; + LastLoginPropertyTypeAlias = Constants.Conventions.Member.LastLoginDate; + LastPasswordChangedPropertyTypeAlias = Constants.Conventions.Member.LastPasswordChangeDate; + PasswordRetrievalQuestionPropertyTypeAlias = Constants.Conventions.Member.PasswordQuestion; + PasswordRetrievalAnswerPropertyTypeAlias = Constants.Conventions.Member.PasswordAnswer; + } + #region Fields private string _providerName = Member.UmbracoMemberProviderName; #endregion - + + public string LockPropertyTypeAlias { get; protected set; } + public string LastLockedOutPropertyTypeAlias { get; protected set; } + public string FailedPasswordAttemptsPropertyTypeAlias { get; protected set; } + public string ApprovedPropertyTypeAlias { get; protected set; } + public string CommentPropertyTypeAlias { get; protected set; } + public string LastLoginPropertyTypeAlias { get; protected set; } + public string LastPasswordChangedPropertyTypeAlias { get; protected set; } + public string PasswordRetrievalQuestionPropertyTypeAlias { get; protected set; } + public string PasswordRetrievalAnswerPropertyTypeAlias { get; protected set; } + /// /// Override to maintain backwards compatibility with 0 required non-alphanumeric chars /// @@ -164,17 +187,7 @@ namespace umbraco.providers.members // in order to support updating passwords from the umbraco core, we can't validate the old password var m = Member.GetMemberFromLoginName(username); if (m == null) return false; - - var args = new ValidatePasswordEventArgs(username, newPassword, false); - OnValidatingPassword(args); - - if (args.Cancel) - { - if (args.FailureInformation != null) - throw args.FailureInformation; - throw new MembershipPasswordException("Change password canceled due to password validation failure."); - } - + string salt; var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); m.ChangePassword( @@ -199,6 +212,11 @@ namespace umbraco.providers.members /// protected override bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) { + if (string.IsNullOrEmpty(PasswordRetrievalQuestionPropertyTypeAlias) || string.IsNullOrEmpty(PasswordRetrievalAnswerPropertyTypeAlias)) + { + throw new NotSupportedException("Updating the password Question and Answer is not valid if the properties aren't set in the config file"); + } + var m = Member.GetMemberFromLoginName(username); if (m == null) { @@ -409,50 +427,45 @@ namespace umbraco.providers.members /// /// The password for the specified user name. /// - public override string GetPassword(string username, string answer) + protected override string PerformGetPassword(string username, string answer) { - if (EnablePasswordRetrieval == false) - throw new ProviderException("Password Retrieval Not Enabled."); - - if (PasswordFormat == MembershipPasswordFormat.Hashed) - throw new ProviderException("Cannot retrieve Hashed passwords."); - var m = Member.GetMemberFromLoginName(username); - if (m != null) - { - if (RequiresQuestionAndAnswer) - { - // check if password answer property alias is set - if (string.IsNullOrEmpty(PasswordRetrievalAnswerPropertyTypeAlias) == false) - { - // check if user is locked out - if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) - { - var isLockedOut = false; - bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLockedOut); - if (isLockedOut) - { - throw new MembershipPasswordException("The supplied user is locked out"); - } - } - - // match password answer - if (GetMemberProperty(m, PasswordRetrievalAnswerPropertyTypeAlias, false) != answer) - { - throw new MembershipPasswordException("Incorrect password answer"); - } - } - else - { - throw new ProviderException("Password retrieval answer property alias is not set! To automatically support password question/answers you'll need to add references to the membertype properties in the 'Member' element in web.config by adding their aliases to the 'umbracoPasswordRetrievalQuestionPropertyTypeAlias' and 'umbracoPasswordRetrievalAnswerPropertyTypeAlias' attributes"); - } - } - } if (m == null) { throw new MembershipPasswordException("The supplied user is not found"); } - return m.GetPassword(); + + // check if user is locked out + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) + { + var isLockedOut = false; + bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLockedOut); + if (isLockedOut) + { + throw new MembershipPasswordException("The supplied user is locked out"); + } + } + + if (RequiresQuestionAndAnswer) + { + // check if password answer property alias is set + if (string.IsNullOrEmpty(PasswordRetrievalAnswerPropertyTypeAlias) == false) + { + // match password answer + if (GetMemberProperty(m, PasswordRetrievalAnswerPropertyTypeAlias, false) != answer) + { + throw new MembershipPasswordException("Incorrect password answer"); + } + } + else + { + throw new ProviderException("Password retrieval answer property alias is not set! To automatically support password question/answers you'll need to add references to the membertype properties in the 'Member' element in web.config by adding their aliases to the 'umbracoPasswordRetrievalQuestionPropertyTypeAlias' and 'umbracoPasswordRetrievalAnswerPropertyTypeAlias' attributes"); + } + } + + var decodedPassword = DecodePassword(m.GetPassword()); + + return decodedPassword; } /// @@ -465,11 +478,21 @@ namespace umbraco.providers.members /// public override MembershipUser GetUser(string username, bool userIsOnline) { - if (String.IsNullOrEmpty(username)) + if (string.IsNullOrEmpty(username)) return null; - Member m = Member.GetMemberFromLoginName(username); - if (m == null) return null; - else return ConvertToMembershipUser(m); + var m = Member.GetMemberFromLoginName(username); + + if (m == null) + { + return null; + } + + if (userIsOnline && LastLoginPropertyTypeAlias.IsNullOrWhiteSpace() == false) + { + UpdateMemberProperty(m, LastLoginPropertyTypeAlias, DateTime.Now); + } + + return ConvertToMembershipUser(m); } /// @@ -486,14 +509,23 @@ namespace umbraco.providers.members if (asGuid.Success) { var m = new Member(asGuid.Result); + if (userIsOnline && LastLoginPropertyTypeAlias.IsNullOrWhiteSpace() == false) + { + UpdateMemberProperty(m, LastLoginPropertyTypeAlias, DateTime.Now); + } return ConvertToMembershipUser(m); } var asInt = providerUserKey.TryConvertTo(); if (asInt.Success) { var m = new Member(asInt.Result); + if (userIsOnline && LastLoginPropertyTypeAlias.IsNullOrWhiteSpace() == false) + { + UpdateMemberProperty(m, LastLoginPropertyTypeAlias, DateTime.Now); + } return ConvertToMembershipUser(m); } + throw new InvalidOperationException("The " + GetType() + " provider only supports GUID or Int as a providerUserKey"); } @@ -508,7 +540,7 @@ namespace umbraco.providers.members /// public override string GetUserNameByEmail(string email) { - Member m = Member.GetMemberFromEmail(email); + var m = Member.GetMemberFromEmail(email); return m == null ? null : m.LoginName; } @@ -517,14 +549,10 @@ namespace umbraco.providers.members /// /// The user to reset the password for. /// The password answer for the specified user (not used with Umbraco). + /// /// The new password for the specified user. - public override string ResetPassword(string username, string answer) + protected override string PerformResetPassword(string username, string answer, string generatedPassword) { - if (EnablePasswordReset == false) - { - throw new NotSupportedException("Password reset is not supported"); - } - //TODO: This should be here - but how do we update failure count in this provider?? //if (answer == null && RequiresQuestionAndAnswer) //{ @@ -532,42 +560,33 @@ namespace umbraco.providers.members // throw new ProviderException("Password answer required for password reset."); //} - - var newPassword = Membership.GeneratePassword(MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters); - - var args = new ValidatePasswordEventArgs(username, newPassword, true); - OnValidatingPassword(args); - if (args.Cancel) - { - if (args.FailureInformation != null) - throw args.FailureInformation; - throw new MembershipPasswordException("Reset password canceled due to password validation failure."); - } var m = Member.GetMemberFromLoginName(username); if (m == null) - throw new MembershipPasswordException("The supplied user is not found"); + { + throw new ProviderException("The supplied user is not found"); + } + + // check if user is locked out + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) + { + var isLockedOut = false; + bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLockedOut); + if (isLockedOut) + { + throw new ProviderException("The member is locked out."); + } + } if (RequiresQuestionAndAnswer) { // check if password answer property alias is set if (string.IsNullOrEmpty(PasswordRetrievalAnswerPropertyTypeAlias) == false) { - // check if user is locked out - if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) - { - var isLockedOut = false; - bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLockedOut); - if (isLockedOut) - { - throw new MembershipPasswordException("The supplied user is locked out"); - } - } - // match password answer if (GetMemberProperty(m, PasswordRetrievalAnswerPropertyTypeAlias, false) != answer) { - throw new MembershipPasswordException("Incorrect password answer"); + throw new ProviderException("Incorrect password answer"); } } else @@ -577,10 +596,9 @@ namespace umbraco.providers.members } string salt; - var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); + var encodedPassword = EncryptOrHashNewPassword(generatedPassword, out salt); //set the password on the member - m.ChangePassword( - FormatPasswordForStorage(encodedPassword, salt)); + m.ChangePassword(FormatPasswordForStorage(encodedPassword, salt)); if (string.IsNullOrEmpty(LastPasswordChangedPropertyTypeAlias) == false) { @@ -589,7 +607,7 @@ namespace umbraco.providers.members m.Save(); - return newPassword; + return generatedPassword; } /// @@ -607,10 +625,14 @@ namespace umbraco.providers.members if (m != null) { UpdateMemberProperty(m, LockPropertyTypeAlias, 0); + if (string.IsNullOrEmpty(FailedPasswordAttemptsPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, FailedPasswordAttemptsPropertyTypeAlias, 0); + } m.Save(); return true; } - throw new Exception(String.Format("No member with the username '{0}' found", userName)); + throw new ProviderException(string.Format("No member with the username '{0}' found", userName)); } throw new ProviderException("To enable lock/unlocking, you need to add a 'bool' property on your membertype and add the alias of the property in the 'umbracoLockPropertyTypeAlias' attribute of the membership element in the web.config."); } @@ -622,6 +644,12 @@ namespace umbraco.providers.members public override void UpdateUser(MembershipUser user) { var m = Member.GetMemberFromLoginName(user.UserName); + + if (m == null) + { + throw new ProviderException(string.Format("No member with the username '{0}' found", user.UserName)); + } + m.Email = user.Email; // if supported, update approve status @@ -640,6 +668,86 @@ namespace umbraco.providers.members m.Save(); } + /// + /// Verifies that the specified user name and password exist in the data source. + /// + /// The name of the user to validate. + /// The password for the specified user. + /// + /// true if the specified username and password are valid; otherwise, false. + /// + public override bool ValidateUser(string username, string password) + { + var m = Member.GetMemberFromLoginAndEncodedPassword(username, EncryptOrHashExistingPassword(password)); + if (m != null) + { + // check for lock status. If locked, then set the member property to null + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) + { + string lockedStatus = GetMemberProperty(m, LockPropertyTypeAlias, true); + if (string.IsNullOrEmpty(lockedStatus) == false) + { + var isLocked = false; + if (bool.TryParse(lockedStatus, out isLocked)) + { + if (isLocked) + { + LogHelper.Info("Cannot validate member " + username + " because they are currently locked out"); + m = null; + } + } + } + } + + //check for approve status. If not approved, then set the member property to null + if (m != null && CheckApproveStatus(m) == false) + { + LogHelper.Info("Cannot validate member " + username + " because they are not approved"); + m = null; + } + + // maybe update login date + if (m != null && string.IsNullOrEmpty(LastLoginPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, LastLoginPropertyTypeAlias, DateTime.Now); + } + + // maybe reset password attempts + if (m != null && string.IsNullOrEmpty(FailedPasswordAttemptsPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, FailedPasswordAttemptsPropertyTypeAlias, 0); + } + + // persist data + if (m != null) + m.Save(); + } + else if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false + && string.IsNullOrEmpty(FailedPasswordAttemptsPropertyTypeAlias) == false) + { + var updateMemberDataObject = Member.GetMemberFromLoginName(username); + // update fail rate if it's approved + if (updateMemberDataObject != null && CheckApproveStatus(updateMemberDataObject)) + { + int failedAttempts = 0; + int.TryParse(GetMemberProperty(updateMemberDataObject, FailedPasswordAttemptsPropertyTypeAlias, false), out failedAttempts); + failedAttempts = failedAttempts + 1; + UpdateMemberProperty(updateMemberDataObject, FailedPasswordAttemptsPropertyTypeAlias, failedAttempts); + + // lock user? + if (failedAttempts >= MaxInvalidPasswordAttempts) + { + UpdateMemberProperty(updateMemberDataObject, LockPropertyTypeAlias, 1); + UpdateMemberProperty(updateMemberDataObject, LastLockedOutPropertyTypeAlias, DateTime.Now); + LogHelper.Info("Member " + username + " is now locked out, max invalid password attempts exceeded"); + } + updateMemberDataObject.Save(); + } + + } + return (m != null); + } + private static void UpdateMemberProperty(Member m, string propertyTypeAlias, object propertyValue) { if (string.IsNullOrEmpty(propertyTypeAlias) == false) @@ -688,83 +796,7 @@ namespace umbraco.providers.members return null; } - - /// - /// Verifies that the specified user name and password exist in the data source. - /// - /// The name of the user to validate. - /// The password for the specified user. - /// - /// true if the specified username and password are valid; otherwise, false. - /// - public override bool ValidateUser(string username, string password) - { - var m = Member.GetMemberFromLoginAndEncodedPassword(username, EncryptOrHashExistingPassword(password)); - if (m != null) - { - // check for lock status. If locked, then set the member property to null - if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) - { - string lockedStatus = GetMemberProperty(m, LockPropertyTypeAlias, true); - if (string.IsNullOrEmpty(lockedStatus) == false) - { - var isLocked = false; - if (bool.TryParse(lockedStatus, out isLocked)) - { - if (isLocked) - { - m = null; - } - } - } - } - - //check for approve status. If not approved, then set the member property to null - if (m != null && !CheckApproveStatus(m)) { - m = null; - } - - // maybe update login date - if (m != null && string.IsNullOrEmpty(LastLoginPropertyTypeAlias) == false) - { - UpdateMemberProperty(m, LastLoginPropertyTypeAlias, DateTime.Now); - } - - // maybe reset password attempts - if (m != null && string.IsNullOrEmpty(FailedPasswordAttemptsPropertyTypeAlias) == false) - { - UpdateMemberProperty(m, FailedPasswordAttemptsPropertyTypeAlias, 0); - } - - // persist data - if (m != null) - m.Save(); - } - else if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false - && string.IsNullOrEmpty(FailedPasswordAttemptsPropertyTypeAlias) == false) - { - var updateMemberDataObject = Member.GetMemberFromLoginName(username); - // update fail rate if it's approved - if (updateMemberDataObject != null && CheckApproveStatus(updateMemberDataObject)) - { - int failedAttempts = 0; - int.TryParse(GetMemberProperty(updateMemberDataObject, FailedPasswordAttemptsPropertyTypeAlias, false), out failedAttempts); - failedAttempts = failedAttempts+1; - UpdateMemberProperty(updateMemberDataObject, FailedPasswordAttemptsPropertyTypeAlias, failedAttempts); - - // lock user? - if (failedAttempts >= MaxInvalidPasswordAttempts) - { - UpdateMemberProperty(updateMemberDataObject, LockPropertyTypeAlias, 1); - UpdateMemberProperty(updateMemberDataObject, LastLockedOutPropertyTypeAlias, DateTime.Now); - } - updateMemberDataObject.Save(); - } - - } - return (m != null); - } - + private bool CheckApproveStatus(Member m) { var isApproved = false; @@ -834,7 +866,7 @@ namespace umbraco.providers.members /// /// The password. /// The encoded password. - [Obsolete("Do not use this, it is the legacy way to encode a password")] + [Obsolete("Do not use this, it is the legacy way to encode a password - use the base class EncryptOrHashExistingPassword instead")] public string EncodePassword(string password) { return LegacyEncodePassword(password); @@ -845,7 +877,7 @@ namespace umbraco.providers.members /// /// The encoded password. /// The unencoded password. - [Obsolete("Do not use this, it is the legacy way to decode a password")] + [Obsolete("Do not use this, it is the legacy way to decode a password - use the base class DecodePassword instead")] public string UnEncodePassword(string encodedPassword) { return LegacyUnEncodePassword(encodedPassword); From 0f68818a1924072b25e1d2123bdd727ad47ed51e Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 20 Dec 2013 15:44:53 +1100 Subject: [PATCH 16/27] Updated member service with the newer property value queries and added unit tests for all of them --- src/Umbraco.Core/Services/MemberService.cs | 94 ++++++- .../Membership/MembershipProviderBaseTests.cs | 6 + .../Services/MemberServiceTests.cs | 258 +++++++++++++++++- .../TestHelpers/Entities/MockedMember.cs | 9 +- 4 files changed, 351 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index a5333cc503..d732953863 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -316,12 +316,49 @@ namespace Umbraco.Core.Services { using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) { - var query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).IntegerropertyValue == value); + IQuery query; + switch (matchType) + { + case ValuePropertyMatchType.Exact: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member) x).IntegerropertyValue == value); + break; + case ValuePropertyMatchType.GreaterThan: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member) x).IntegerropertyValue > value); + break; + case ValuePropertyMatchType.LessThan: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member) x).IntegerropertyValue < value); + break; + case ValuePropertyMatchType.GreaterThanOrEqualTo: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member) x).IntegerropertyValue >= value); + break; + case ValuePropertyMatchType.LessThanOrEqualTo: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member) x).IntegerropertyValue <= value); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } + var members = repository.GetByQuery(query); return members; } @@ -359,11 +396,48 @@ namespace Umbraco.Core.Services { using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) { - var query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue == value); + IQuery query; + + switch (matchType) + { + case ValuePropertyMatchType.Exact: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member) x).DateTimePropertyValue == value); + break; + case ValuePropertyMatchType.GreaterThan: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member) x).DateTimePropertyValue > value); + break; + case ValuePropertyMatchType.LessThan: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member) x).DateTimePropertyValue < value); + break; + case ValuePropertyMatchType.GreaterThanOrEqualTo: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member) x).DateTimePropertyValue >= value); + break; + case ValuePropertyMatchType.LessThanOrEqualTo: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue <= value); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } var members = repository.GetByQuery(query); return members; diff --git a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs index f1b8d66773..cf29e71eab 100644 --- a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs +++ b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs @@ -35,6 +35,12 @@ namespace Umbraco.Tests.Membership //[Test] //public void CreateUser_Base_Validation() + //[Test] + //public void GetPassword_Base_Validation() + + //[Test] + //public void ResetPassword_Base_Validation() + [Test] public void Sets_Defaults() { diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 54fa355ce5..3fe0940b42 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; @@ -239,7 +240,7 @@ namespace Umbraco.Tests.Services } [Test] - public void Get_By_Property_Value_Exact() + public void Get_By_Property_String_Value_Exact() { IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); ServiceContext.MemberTypeService.Save(memberType); @@ -255,7 +256,7 @@ namespace Umbraco.Tests.Services } [Test] - public void Get_By_Property_Value_Contains() + public void Get_By_Property_String_Value_Contains() { IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); ServiceContext.MemberTypeService.Save(memberType); @@ -271,7 +272,7 @@ namespace Umbraco.Tests.Services } [Test] - public void Get_By_Property_Value_Starts_With() + public void Get_By_Property_String_Value_Starts_With() { IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); ServiceContext.MemberTypeService.Save(memberType); @@ -287,7 +288,7 @@ namespace Umbraco.Tests.Services } [Test] - public void Get_By_Property_Value_Ends_With() + public void Get_By_Property_String_Value_Ends_With() { IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); ServiceContext.MemberTypeService.Save(memberType); @@ -303,6 +304,255 @@ namespace Umbraco.Tests.Services Assert.AreEqual(1, found.Count()); } + [Test] + public void Get_By_Property_Int_Value_Exact() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.AddPropertyType(new PropertyType(new Guid(), DataTypeDatabaseType.Integer) + { + Alias = "number", + Name = "Number", + //NOTE: This is what really determines the db type - the above definition doesn't really do anything + DataTypeDefinitionId = -36 + }, "Content"); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.SetValue("number", i)); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("number", 2); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "number", 2, ValuePropertyMatchType.Exact); + + Assert.AreEqual(2, found.Count()); + } + + [Test] + public void Get_By_Property_Int_Value_Greater_Than() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.AddPropertyType(new PropertyType(new Guid(), DataTypeDatabaseType.Integer) + { + Alias = "number", + Name = "Number", + //NOTE: This is what really determines the db type - the above definition doesn't really do anything + DataTypeDefinitionId = -36 + }, "Content"); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.SetValue("number", i)); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("number", 10); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "number", 3, ValuePropertyMatchType.GreaterThan); + + Assert.AreEqual(7, found.Count()); + } + + [Test] + public void Get_By_Property_Int_Value_Greater_Than_Equal_To() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.AddPropertyType(new PropertyType(new Guid(), DataTypeDatabaseType.Integer) + { + Alias = "number", + Name = "Number", + //NOTE: This is what really determines the db type - the above definition doesn't really do anything + DataTypeDefinitionId = -36 + }, "Content"); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.SetValue("number", i)); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("number", 10); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "number", 3, ValuePropertyMatchType.GreaterThanOrEqualTo); + + Assert.AreEqual(8, found.Count()); + } + + [Test] + public void Get_By_Property_Int_Value_Less_Than() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.AddPropertyType(new PropertyType(new Guid(), DataTypeDatabaseType.Date) + { + Alias = "date", + Name = "Date", + //NOTE: This is what really determines the db type - the above definition doesn't really do anything + DataTypeDefinitionId = -36 + }, "Content"); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.SetValue("number", i)); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("number", 1); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "number", 5, ValuePropertyMatchType.LessThan); + + Assert.AreEqual(6, found.Count()); + } + + [Test] + public void Get_By_Property_Int_Value_Less_Than_Or_Equal() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.AddPropertyType(new PropertyType(new Guid(), DataTypeDatabaseType.Integer) + { + Alias = "number", + Name = "Number", + //NOTE: This is what really determines the db type - the above definition doesn't really do anything + DataTypeDefinitionId = -36 + }, "Content"); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.SetValue("number", i)); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("number", 1); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "number", 5, ValuePropertyMatchType.LessThanOrEqualTo); + + Assert.AreEqual(7, found.Count()); + } + + [Test] + public void Get_By_Property_Date_Value_Exact() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.AddPropertyType(new PropertyType(new Guid(), DataTypeDatabaseType.Integer) + { + Alias = "date", + Name = "Date", + //NOTE: This is what really determines the db type - the above definition doesn't really do anything + DataTypeDefinitionId = -36 + }, "Content"); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.SetValue("date", new DateTime(2013, 12, 20, 1, i, 0))); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("date", new DateTime(2013, 12, 20, 1, 2, 0)); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "date", new DateTime(2013, 12, 20, 1, 2, 0), ValuePropertyMatchType.Exact); + + Assert.AreEqual(2, found.Count()); + } + + [Test] + public void Get_By_Property_Date_Value_Greater_Than() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.AddPropertyType(new PropertyType(new Guid(), DataTypeDatabaseType.Integer) + { + Alias = "date", + Name = "Date", + //NOTE: This is what really determines the db type - the above definition doesn't really do anything + DataTypeDefinitionId = -36 + }, "Content"); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.SetValue("date", new DateTime(2013, 12, 20, 1, i, 0))); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("date", new DateTime(2013, 12, 20, 1, 10, 0)); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "date", new DateTime(2013, 12, 20, 1, 3, 0), ValuePropertyMatchType.GreaterThan); + + Assert.AreEqual(7, found.Count()); + } + + [Test] + public void Get_By_Property_Date_Value_Greater_Than_Equal_To() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.AddPropertyType(new PropertyType(new Guid(), DataTypeDatabaseType.Integer) + { + Alias = "date", + Name = "Date", + //NOTE: This is what really determines the db type - the above definition doesn't really do anything + DataTypeDefinitionId = -36 + }, "Content"); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.SetValue("date", new DateTime(2013, 12, 20, 1, i, 0))); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("date", new DateTime(2013, 12, 20, 1, 10, 0)); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "date", new DateTime(2013, 12, 20, 1, 3, 0), ValuePropertyMatchType.GreaterThanOrEqualTo); + + Assert.AreEqual(8, found.Count()); + } + + [Test] + public void Get_By_Property_Date_Value_Less_Than() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.AddPropertyType(new PropertyType(new Guid(), DataTypeDatabaseType.Integer) + { + Alias = "date", + Name = "Date", + //NOTE: This is what really determines the db type - the above definition doesn't really do anything + DataTypeDefinitionId = -36 + }, "Content"); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.SetValue("date", new DateTime(2013, 12, 20, 1, i, 0))); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("date", new DateTime(2013, 12, 20, 1, 1, 0)); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "date", new DateTime(2013, 12, 20, 1, 5, 0), ValuePropertyMatchType.LessThan); + + Assert.AreEqual(6, found.Count()); + } + + [Test] + public void Get_By_Property_Date_Value_Less_Than_Or_Equal() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.AddPropertyType(new PropertyType(new Guid(), DataTypeDatabaseType.Integer) + { + Alias = "date", + Name = "Date", + //NOTE: This is what really determines the db type - the above definition doesn't really do anything + DataTypeDefinitionId = -36 + }, "Content"); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.SetValue("date", new DateTime(2013, 12, 20, 1, i, 0))); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("date", new DateTime(2013, 12, 20, 1, 1, 0)); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "date", new DateTime(2013, 12, 20, 1, 5, 0), ValuePropertyMatchType.LessThanOrEqualTo); + + Assert.AreEqual(7, found.Count()); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedMember.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedMember.cs index 0aa224d635..2bf06ec87a 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedMember.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedMember.cs @@ -31,7 +31,7 @@ namespace Umbraco.Tests.TestHelpers.Entities return member; } - public static IEnumerable CreateSimpleMember(IMemberType memberType, int amount) + public static IEnumerable CreateSimpleMember(IMemberType memberType, int amount, Action onCreating = null) { var list = new List(); @@ -43,8 +43,13 @@ namespace Umbraco.Tests.TestHelpers.Entities member.SetValue("bodyText", "This is a subpage" + i); member.SetValue("author", "John Doe" + i); - member.ResetDirtyProperties(false); + if (onCreating != null) + { + onCreating(i, member); + } + member.ResetDirtyProperties(false); + list.Add(member); } From a4dde1948f5a5ee7c69844488d97b18d936e2c61 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 20 Dec 2013 16:20:32 +1100 Subject: [PATCH 17/27] Implements the member count methods on the member service and adds unit tests. --- .../Interfaces/IMemberRepository.cs | 8 ++ .../Repositories/MemberRepository.cs | 12 +++ src/Umbraco.Core/Services/MemberService.cs | 90 +++++++++++++------ .../Services/MemberServiceTests.cs | 67 ++++++++++++++ 4 files changed, 150 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs index ed98d81551..c37997769e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { @@ -19,5 +20,12 @@ namespace Umbraco.Core.Persistence.Repositories /// bool Exists(string username); + /// + /// Gets the count of items based on a complex query + /// + /// + /// + int GetCountByQuery(IQuery query); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index ba28e68f02..6fb99d0d13 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -448,6 +448,18 @@ namespace Umbraco.Core.Persistence.Repositories return Database.ExecuteScalar(sql) > 0; } + public int GetCountByQuery(IQuery query) + { + var sqlSubquery = GetSubquery(); + var translator = new SqlTranslator(sqlSubquery, query); + var subquery = translator.Translate(); + //get the COUNT base query + var sql = GetBaseQuery(true) + .Append(new Sql("WHERE umbracoNode.id IN (" + subquery.SQL + ")", subquery.Arguments)); + + return Database.ExecuteScalar(sql); + } + private IMember BuildFromDto(List dtos) { if (dtos == null || dtos.Any() == false) diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index d732953863..f5474288ff 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; +using System.Web.Security; using System.Xml.Linq; using Umbraco.Core.Events; using Umbraco.Core.Models; @@ -75,7 +76,7 @@ namespace Umbraco.Core.Services public IMember GetById(int id) { using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) - { + { return repository.Get(id); } } @@ -268,7 +269,7 @@ namespace Umbraco.Core.Services query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).PropertyTypeAlias == propertyTypeAlias && (((Member)x).LongStringPropertyValue.SqlEquals(value, TextColumnType.NText) || ((Member)x).ShortStringPropertyValue.SqlEquals(value, TextColumnType.NVarchar))); break; @@ -276,7 +277,7 @@ namespace Umbraco.Core.Services query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).PropertyTypeAlias == propertyTypeAlias && (((Member)x).LongStringPropertyValue.SqlContains(value, TextColumnType.NText) || ((Member)x).ShortStringPropertyValue.SqlContains(value, TextColumnType.NVarchar))); break; @@ -284,7 +285,7 @@ namespace Umbraco.Core.Services query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).PropertyTypeAlias == propertyTypeAlias && (((Member)x).LongStringPropertyValue.SqlStartsWith(value, TextColumnType.NText) || ((Member)x).ShortStringPropertyValue.SqlStartsWith(value, TextColumnType.NVarchar))); break; @@ -292,7 +293,7 @@ namespace Umbraco.Core.Services query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).PropertyTypeAlias == propertyTypeAlias && (((Member)x).LongStringPropertyValue.SqlEndsWith(value, TextColumnType.NText) || ((Member)x).ShortStringPropertyValue.SqlEndsWith(value, TextColumnType.NVarchar))); break; @@ -324,41 +325,41 @@ namespace Umbraco.Core.Services query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && - ((Member) x).IntegerropertyValue == value); + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerropertyValue == value); break; case ValuePropertyMatchType.GreaterThan: query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && - ((Member) x).IntegerropertyValue > value); + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerropertyValue > value); break; case ValuePropertyMatchType.LessThan: query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && - ((Member) x).IntegerropertyValue < value); + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerropertyValue < value); break; case ValuePropertyMatchType.GreaterThanOrEqualTo: query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && - ((Member) x).IntegerropertyValue >= value); + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerropertyValue >= value); break; case ValuePropertyMatchType.LessThanOrEqualTo: query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && - ((Member) x).IntegerropertyValue <= value); + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerropertyValue <= value); break; default: throw new ArgumentOutOfRangeException("matchType"); } - + var members = repository.GetByQuery(query); return members; } @@ -404,29 +405,29 @@ namespace Umbraco.Core.Services query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && - ((Member) x).DateTimePropertyValue == value); + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue == value); break; case ValuePropertyMatchType.GreaterThan: query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && - ((Member) x).DateTimePropertyValue > value); + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue > value); break; case ValuePropertyMatchType.LessThan: query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && - ((Member) x).DateTimePropertyValue < value); + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue < value); break; case ValuePropertyMatchType.GreaterThanOrEqualTo: query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && - ((Member) x).DateTimePropertyValue >= value); + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue >= value); break; case ValuePropertyMatchType.LessThanOrEqualTo: query = @@ -443,7 +444,7 @@ namespace Umbraco.Core.Services return members; } } - + #endregion #region IMembershipMemberService Implementation @@ -460,7 +461,42 @@ namespace Umbraco.Core.Services /// public int GetMemberCount(MemberCountType countType) { - throw new NotImplementedException(); + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + IQuery query; + + switch (countType) + { + case MemberCountType.All: + query = new Query(); + return repository.Count(query); + case MemberCountType.Online: + var fromDate = DateTime.Now.AddMinutes(-Membership.UserIsOnlineTimeWindow); + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.LastLoginDate && + ((Member)x).DateTimePropertyValue > fromDate); + return repository.GetCountByQuery(query); + case MemberCountType.LockedOut: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.IsLockedOut && + ((Member)x).BoolPropertyValue == true); + return repository.GetCountByQuery(query); + case MemberCountType.Approved: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.IsApproved && + ((Member)x).BoolPropertyValue == true); + return repository.GetCountByQuery(query); + default: + throw new ArgumentOutOfRangeException("countType"); + } + } + } public IEnumerable GetAllMembers(int pageIndex, int pageSize, out int totalRecords) @@ -610,7 +646,7 @@ namespace Umbraco.Core.Services return; } using (new WriteLock(Locker)) - { + { var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateMemberRepository(uow)) { diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 3fe0940b42..873e5c189d 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Querying; @@ -554,5 +555,71 @@ namespace Umbraco.Tests.Services Assert.AreEqual(7, found.Count()); } + [Test] + public void Count_All_Members() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10); + ServiceContext.MemberService.Save(members); + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMemberCount(MemberCountType.All); + + Assert.AreEqual(11, found); + } + + [Test] + public void Count_All_Online_Members() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.LastLoginDate = DateTime.Now.AddMinutes(i * -2)); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue(Constants.Conventions.Member.LastLoginDate, DateTime.Now); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMemberCount(MemberCountType.Online); + + Assert.AreEqual(9, found); + } + + [Test] + public void Count_All_Locked_Members() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.IsLockedOut = i % 2 == 0); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue(Constants.Conventions.Member.IsLockedOut, true); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMemberCount(MemberCountType.LockedOut); + + Assert.AreEqual(6, found); + } + + [Test] + public void Count_All_Approved_Members() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.IsApproved = i % 2 == 0); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue(Constants.Conventions.Member.IsApproved, false); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMemberCount(MemberCountType.Approved); + + Assert.AreEqual(5, found); + } + } } \ No newline at end of file From 7a8b3dc9bcf24dcc7394c3c28fadbef829d0d7d0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 20 Dec 2013 18:11:12 +1100 Subject: [PATCH 18/27] Updated the member service/repo to have paged queries and results which use db paging for maximum efficiency (of course only works for things like email, username but those are 2 specific things that are needed by the membership provider) --- .../Interfaces/IMemberRepository.cs | 16 ++++++- .../Repositories/MemberRepository.cs | 47 +++++++++++++++++++ .../Services/IMembershipMemberService.cs | 4 +- src/Umbraco.Core/Services/MemberService.cs | 15 ++++-- .../Services/MemberServiceTests.cs | 24 ++++++---- .../Providers/MembersMembershipProvider.cs | 18 +++---- .../members/UmbracoMembershipProvider.cs | 18 +++---- 7 files changed, 111 insertions(+), 31 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs index c37997769e..1045182cf5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs @@ -1,5 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories @@ -27,5 +30,16 @@ namespace Umbraco.Core.Persistence.Repositories /// int GetCountByQuery(IQuery query); + /// + /// Gets paged member results + /// + /// + /// + /// + /// + /// + /// + IEnumerable GetPagedResultsByQuery(IQuery query, int pageIndex, int pageSize, out int totalRecords, Expression> orderBy); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index 6fb99d0d13..58996209fa 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Linq.Expressions; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Models.EntityBase; @@ -9,6 +10,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Caching; using Umbraco.Core.Persistence.Factories; +using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.UnitOfWork; @@ -460,6 +462,51 @@ namespace Umbraco.Core.Persistence.Repositories return Database.ExecuteScalar(sql); } + /// + /// Gets paged member results + /// + /// + /// + /// + /// + /// + /// + /// + /// The query supplied will ONLY work with data specifically on the cmsMember table because we are using PetaPoco paging (SQL paging) + /// + public IEnumerable GetPagedResultsByQuery(IQuery query, int pageIndex, int pageSize, out int totalRecords, Expression> orderBy) + { + if (orderBy == null) throw new ArgumentNullException("orderBy"); + + var sql = new Sql(); + sql.Select("*").From(); + + var translator = new SqlTranslator(sql, query); + var resultQuery = translator.Translate(); + + //get the referenced column name + var expressionMember = ExpressionHelper.GetMemberInfo(orderBy); + //now find the mapped column name + var mapper = MappingResolver.Current.ResolveMapperByType(typeof(IMember)); + var mappedField = mapper.Map(expressionMember.Name); + if (mappedField.IsNullOrWhiteSpace()) + { + throw new ArgumentException("Could not find a mapping for the column specified in the orderBy clause"); + } + //need to ensure the order by is in brackets, see: https://github.com/toptensoftware/PetaPoco/issues/177 + resultQuery.OrderBy(string.Format("({0})", mappedField)); + + var pagedResult = Database.Page(pageIndex + 1, pageSize, resultQuery); + + totalRecords = Convert.ToInt32(pagedResult.TotalItems); + + //now that we have the member dto's we need to construct true members from the list. + var result = GetAll(pagedResult.Items.Select(x => x.NodeId).ToArray()); + + //now we need to ensure this result is also ordered by the same order by clause + return result.OrderBy(orderBy.Compile()); + } + private IMember BuildFromDto(List dtos) { if (dtos == null || dtos.Any() == false) diff --git a/src/Umbraco.Core/Services/IMembershipMemberService.cs b/src/Umbraco.Core/Services/IMembershipMemberService.cs index 715f85f3fb..6c3027d21c 100644 --- a/src/Umbraco.Core/Services/IMembershipMemberService.cs +++ b/src/Umbraco.Core/Services/IMembershipMemberService.cs @@ -46,9 +46,9 @@ namespace Umbraco.Core.Services void Save(IEnumerable members, bool raiseEvents = true); - IEnumerable FindMembersByEmail(string emailStringToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + IEnumerable FindMembersByEmail(string emailStringToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); - IEnumerable FindMembersByUsername(string login, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + IEnumerable FindMembersByUsername(string login, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); /// /// Gets the total number of members based on the count type diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index f5474288ff..d180d13e82 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -184,9 +184,12 @@ namespace Umbraco.Core.Services /// Does a search for members that contain the specified string in their email address /// /// + /// /// + /// + /// /// - public IEnumerable FindMembersByEmail(string emailStringToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + public IEnumerable FindMembersByEmail(string emailStringToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateMemberRepository(uow)) @@ -214,11 +217,13 @@ namespace Umbraco.Core.Services throw new ArgumentOutOfRangeException("matchType"); } - return repository.GetByQuery(query); + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, dto => dto.Email); + + //return repository.GetByQuery(query); } } - public IEnumerable FindMembersByUsername(string login, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + public IEnumerable FindMembersByUsername(string login, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateMemberRepository(uow)) @@ -246,7 +251,9 @@ namespace Umbraco.Core.Services throw new ArgumentOutOfRangeException("matchType"); } - return repository.GetByQuery(query); + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, dto => dto.Username); + + //return repository.GetByQuery(query); } } diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 873e5c189d..76f565223e 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -123,7 +123,8 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello","hello"); ServiceContext.MemberService.Save(customMember); - var found = ServiceContext.MemberService.FindMembersByEmail("tes", StringPropertyMatchType.StartsWith); + int totalRecs; + var found = ServiceContext.MemberService.FindMembersByEmail("tes", 0, 100, out totalRecs, StringPropertyMatchType.StartsWith); Assert.AreEqual(10, found.Count()); } @@ -139,7 +140,8 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); ServiceContext.MemberService.Save(customMember); - var found = ServiceContext.MemberService.FindMembersByEmail("test.com", StringPropertyMatchType.EndsWith); + int totalRecs; + var found = ServiceContext.MemberService.FindMembersByEmail("test.com", 0, 100, out totalRecs, StringPropertyMatchType.EndsWith); Assert.AreEqual(11, found.Count()); } @@ -155,7 +157,8 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); ServiceContext.MemberService.Save(customMember); - var found = ServiceContext.MemberService.FindMembersByEmail("test", StringPropertyMatchType.Contains); + int totalRecs; + var found = ServiceContext.MemberService.FindMembersByEmail("test", 0, 100, out totalRecs, StringPropertyMatchType.Contains); Assert.AreEqual(11, found.Count()); } @@ -171,7 +174,8 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); ServiceContext.MemberService.Save(customMember); - var found = ServiceContext.MemberService.FindMembersByEmail("hello@test.com", StringPropertyMatchType.Exact); + int totalRecs; + var found = ServiceContext.MemberService.FindMembersByEmail("hello@test.com", 0, 100, out totalRecs, StringPropertyMatchType.Exact); Assert.AreEqual(1, found.Count()); } @@ -187,7 +191,8 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); ServiceContext.MemberService.Save(customMember); - var found = ServiceContext.MemberService.FindMembersByUsername("tes", StringPropertyMatchType.StartsWith); + int totalRecs; + var found = ServiceContext.MemberService.FindMembersByUsername("tes", 0, 100, out totalRecs, StringPropertyMatchType.StartsWith); Assert.AreEqual(10, found.Count()); } @@ -203,7 +208,8 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); ServiceContext.MemberService.Save(customMember); - var found = ServiceContext.MemberService.FindMembersByUsername("llo", StringPropertyMatchType.EndsWith); + int totalRecs; + var found = ServiceContext.MemberService.FindMembersByUsername("llo", 0, 100, out totalRecs, StringPropertyMatchType.EndsWith); Assert.AreEqual(1, found.Count()); } @@ -219,7 +225,8 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hellotest"); ServiceContext.MemberService.Save(customMember); - var found = ServiceContext.MemberService.FindMembersByUsername("test", StringPropertyMatchType.Contains); + int totalRecs; + var found = ServiceContext.MemberService.FindMembersByUsername("test", 0, 100, out totalRecs, StringPropertyMatchType.Contains); Assert.AreEqual(11, found.Count()); } @@ -235,7 +242,8 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); ServiceContext.MemberService.Save(customMember); - var found = ServiceContext.MemberService.FindMembersByUsername("hello", StringPropertyMatchType.Exact); + int totalRecs; + var found = ServiceContext.MemberService.FindMembersByUsername("hello", 0, 100, out totalRecs, StringPropertyMatchType.Exact); Assert.AreEqual(1, found.Count()); } diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index 133314d31a..d1b9c9a14e 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -203,12 +203,13 @@ namespace Umbraco.Web.Security.Providers /// public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { - var byEmail = MemberService.FindMembersByEmail(emailToMatch, StringPropertyMatchType.Wildcard).ToArray(); - totalRecords = byEmail.Length; - var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); + var byEmail = MemberService.FindMembersByEmail(emailToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); + //totalRecords = byEmail.Length; + //var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); var collection = new MembershipUserCollection(); - foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + //foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + foreach (var m in byEmail) { collection.Add(m.AsConcreteMembershipUser()); } @@ -227,12 +228,13 @@ namespace Umbraco.Web.Security.Providers /// public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { - var byEmail = MemberService.FindMembersByUsername(usernameToMatch, StringPropertyMatchType.Wildcard).ToArray(); - totalRecords = byEmail.Length; - var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); + var byEmail = MemberService.FindMembersByUsername(usernameToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); + //totalRecords = byEmail.Length; + //var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); var collection = new MembershipUserCollection(); - foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + //foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + foreach (var m in byEmail) { collection.Add(m.AsConcreteMembershipUser()); } diff --git a/src/umbraco.providers/members/UmbracoMembershipProvider.cs b/src/umbraco.providers/members/UmbracoMembershipProvider.cs index 500444b3fd..6bac76be81 100644 --- a/src/umbraco.providers/members/UmbracoMembershipProvider.cs +++ b/src/umbraco.providers/members/UmbracoMembershipProvider.cs @@ -343,12 +343,13 @@ namespace umbraco.providers.members /// public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { - var byEmail = ApplicationContext.Current.Services.MemberService.FindMembersByEmail(emailToMatch, StringPropertyMatchType.Wildcard).ToArray(); - totalRecords = byEmail.Length; - var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); + var byEmail = ApplicationContext.Current.Services.MemberService.FindMembersByEmail(emailToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); + //totalRecords = byEmail.Length; + //var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); var collection = new MembershipUserCollection(); - foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + //foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + foreach (var m in byEmail) { collection.Add(ConvertToMembershipUser(m)); } @@ -367,12 +368,13 @@ namespace umbraco.providers.members /// public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { - var byEmail = ApplicationContext.Current.Services.MemberService.FindMembersByUsername(usernameToMatch, StringPropertyMatchType.Wildcard).ToArray(); - totalRecords = byEmail.Length; - var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); + var byEmail = ApplicationContext.Current.Services.MemberService.FindMembersByUsername(usernameToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); + //totalRecords = byEmail.Length; + //var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); var collection = new MembershipUserCollection(); - foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + //foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + foreach (var m in byEmail) { collection.Add(ConvertToMembershipUser(m)); } From 1c139b685af5480820eea0a451a2ea3599215d47 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 20 Dec 2013 18:12:54 +1100 Subject: [PATCH 19/27] updates legacy membership provider to use efficient paging. --- src/Umbraco.Tests/Views/web.config | 55 ------------------- .../Providers/MembersMembershipProvider.cs | 8 +-- .../members/UmbracoMembershipProvider.cs | 29 +++------- 3 files changed, 9 insertions(+), 83 deletions(-) delete mode 100644 src/Umbraco.Tests/Views/web.config diff --git a/src/Umbraco.Tests/Views/web.config b/src/Umbraco.Tests/Views/web.config deleted file mode 100644 index aa5e0eb2e3..0000000000 --- a/src/Umbraco.Tests/Views/web.config +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index d1b9c9a14e..5e263f76a4 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -204,11 +204,8 @@ namespace Umbraco.Web.Security.Providers public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { var byEmail = MemberService.FindMembersByEmail(emailToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); - //totalRecords = byEmail.Length; - //var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); - + var collection = new MembershipUserCollection(); - //foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) foreach (var m in byEmail) { collection.Add(m.AsConcreteMembershipUser()); @@ -229,11 +226,8 @@ namespace Umbraco.Web.Security.Providers public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { var byEmail = MemberService.FindMembersByUsername(usernameToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); - //totalRecords = byEmail.Length; - //var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); var collection = new MembershipUserCollection(); - //foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) foreach (var m in byEmail) { collection.Add(m.AsConcreteMembershipUser()); diff --git a/src/umbraco.providers/members/UmbracoMembershipProvider.cs b/src/umbraco.providers/members/UmbracoMembershipProvider.cs index 6bac76be81..fa3a70804e 100644 --- a/src/umbraco.providers/members/UmbracoMembershipProvider.cs +++ b/src/umbraco.providers/members/UmbracoMembershipProvider.cs @@ -344,11 +344,8 @@ namespace umbraco.providers.members public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { var byEmail = ApplicationContext.Current.Services.MemberService.FindMembersByEmail(emailToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); - //totalRecords = byEmail.Length; - //var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); - - var collection = new MembershipUserCollection(); - //foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + + var collection = new MembershipUserCollection(); foreach (var m in byEmail) { collection.Add(ConvertToMembershipUser(m)); @@ -369,11 +366,8 @@ namespace umbraco.providers.members public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { var byEmail = ApplicationContext.Current.Services.MemberService.FindMembersByUsername(usernameToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); - //totalRecords = byEmail.Length; - //var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); - - var collection = new MembershipUserCollection(); - //foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + + var collection = new MembershipUserCollection(); foreach (var m in byEmail) { collection.Add(ConvertToMembershipUser(m)); @@ -392,22 +386,15 @@ namespace umbraco.providers.members /// public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) { - var counter = 0; - var startIndex = pageSize * pageIndex; - var endIndex = startIndex + pageSize - 1; var membersList = new MembershipUserCollection(); - var memberArray = Member.GetAll; - totalRecords = memberArray.Length; - foreach (var m in memberArray) + var pagedMembers = ApplicationContext.Current.Services.MemberService.GetAllMembers(pageIndex, pageSize, out totalRecords); + + foreach (var m in pagedMembers) { - if (counter >= startIndex) - membersList.Add(ConvertToMembershipUser(m)); - if (counter >= endIndex) break; - counter++; + membersList.Add(ConvertToMembershipUser(m)); } return membersList; - } /// From edaa5ebd4336137db64d327ffd8c3d4deba28201 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 24 Dec 2013 14:03:18 +1100 Subject: [PATCH 20/27] Lots more unit tests for the membership providers and some other fixups - have a new failing test as i need to sort out salting issue with encrypted passwords. --- .../Security/MembershipProviderBase.cs | 72 +--- .../Security/UmbracoMembershipProviderBase.cs | 78 +++++ src/Umbraco.Core/Services/IMemberService.cs | 2 +- .../Services/IMembershipMemberService.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Tests/App.config | 1 + .../Membership/MembershipProviderBaseTests.cs | 327 +++++++++++++----- .../Providers/MembersMembershipProvider.cs | 45 ++- 8 files changed, 348 insertions(+), 180 deletions(-) create mode 100644 src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs index 9d76c8e595..3794327781 100644 --- a/src/Umbraco.Core/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs @@ -11,76 +11,6 @@ using Umbraco.Core.Logging; namespace Umbraco.Core.Security { - - public abstract class UmbracoMembershipProviderBase : MembershipProviderBase - { - protected UmbracoMembershipProviderBase() - { - //Set the defaults! - DefaultMemberTypeAlias = "Member"; - } - - public string DefaultMemberTypeAlias { get; protected set; } - - /// - /// Adds a new membership user to the data source. - /// - /// The user name for the new user. - /// The password for the new user. - /// The e-mail address for the new user. - /// The password question for the new user. - /// The password answer for the new user - /// Whether or not the new user is approved to be validated. - /// The unique identifier from the membership data source for the user. - /// A enumeration value indicating whether the user was created successfully. - /// - /// A object populated with the information for the newly created user. - /// - protected sealed override MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) - { - return PerformCreateUser(DefaultMemberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); - } - - /// - /// Adds a new membership user to the data source. - /// - /// The member type alias to use when creating the member - /// The user name for the new user. - /// The password for the new user. - /// The e-mail address for the new user. - /// The password question for the new user. - /// The password answer for the new user - /// Whether or not the new user is approved to be validated. - /// The unique identifier from the membership data source for the user. - /// A enumeration value indicating whether the user was created successfully. - /// - /// A object populated with the information for the newly created user. - /// - public MembershipUser CreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) - { - //do the base validation first - base.CreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); - - return PerformCreateUser(memberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); - } - - /// - /// Adds a new membership user to the data source. - /// - /// The member type alias to use when creating the member - /// The user name for the new user. - /// The password for the new user. - /// The e-mail address for the new user. - /// The password question for the new user. - /// The password answer for the new user - /// Whether or not the new user is approved to be validated. - /// The unique identifier from the membership data source for the user. - /// A enumeration value indicating whether the user was created successfully. - /// - /// A object populated with the information for the newly created user. - /// - protected abstract MembershipUser PerformCreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status); - } /// /// A base membership provider class offering much of the underlying functionality for initializing and password encryption/hashing. /// @@ -741,7 +671,7 @@ namespace Umbraco.Core.Security /// /// /// - protected string EncryptOrHashNewPassword(string newPassword, out string salt) + protected internal string EncryptOrHashNewPassword(string newPassword, out string salt) { salt = GenerateSalt(); return EncryptOrHashPassword(newPassword, salt); diff --git a/src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs b/src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs new file mode 100644 index 0000000000..6ecb6eedce --- /dev/null +++ b/src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs @@ -0,0 +1,78 @@ +using System.Web.Security; + +namespace Umbraco.Core.Security +{ + + /// + /// A base membership provider class for umbraco members (not users) + /// + public abstract class UmbracoMembershipProviderBase : MembershipProviderBase + { + protected UmbracoMembershipProviderBase() + { + //Set the defaults! + DefaultMemberTypeAlias = "Member"; + } + + public string DefaultMemberTypeAlias { get; protected set; } + + /// + /// Adds a new membership user to the data source. + /// + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + protected sealed override MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + { + return PerformCreateUser(DefaultMemberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + } + + /// + /// Adds a new membership user to the data source. + /// + /// The member type alias to use when creating the member + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + public MembershipUser CreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + { + //do the base validation first + base.CreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + + return PerformCreateUser(memberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + } + + /// + /// Adds a new membership user to the data source. + /// + /// The member type alias to use when creating the member + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + protected abstract MembershipUser PerformCreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 87c15573c6..05bcafba38 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.Services /// /// Defines the MemberService, which is an easy access to operations involving (umbraco) members. /// - internal interface IMemberService : IMembershipMemberService + public interface IMemberService : IMembershipMemberService { /// /// Checks if a member with the id exists diff --git a/src/Umbraco.Core/Services/IMembershipMemberService.cs b/src/Umbraco.Core/Services/IMembershipMemberService.cs index 6c3027d21c..0b9ff79665 100644 --- a/src/Umbraco.Core/Services/IMembershipMemberService.cs +++ b/src/Umbraco.Core/Services/IMembershipMemberService.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Services /// /// Idea is to have this is an isolated interface so that it can be easily 'replaced' in the membership provider impl. /// - internal interface IMembershipMemberService : IService + public interface IMembershipMemberService : IService { /// /// Checks if a member with the username exists diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 9f7bd325a1..3119ae7cf0 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -741,6 +741,7 @@ + diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index d86051cdee..0af67e158b 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -82,6 +82,7 @@ + diff --git a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs index cf29e71eab..2af97b49e2 100644 --- a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs +++ b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs @@ -7,8 +7,13 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web.Security; +using Moq; using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; using Umbraco.Core.Security; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Web.Security.Providers; namespace Umbraco.Tests.Membership @@ -20,31 +25,243 @@ namespace Umbraco.Tests.Membership //public void Set_Default_Member_Type_On_Init() //[Test] - //public void Question_Answer_Is_Encrypted() + //public void Create_User_Already_Exists() + //{ + + //} + + //[Test] + //public void Create_User_Requires_Unique_Email() + //{ + + //} + + [Test] + public void Answer_Is_Encrypted() + { + IMember createdMember = null; + var memberType = MockedContentTypes.CreateSimpleMemberType(); + foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) + { + memberType.AddPropertyType(p.Value); + } + var mServiceMock = new Mock(); + mServiceMock.Setup(service => service.Exists("test")).Returns(false); + mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); + mServiceMock.Setup( + service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string u, string e, string p, string m) => + { + createdMember = new Member("test", e, u, p, memberType); + }) + .Returns(() => createdMember); + var provider = new MembersMembershipProvider(mServiceMock.Object); + + MembershipCreateStatus status; + provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); + + Assert.AreNotEqual("test", createdMember.PasswordAnswer); + Assert.AreEqual(provider.EncryptString("test"), createdMember.PasswordAnswer); + } + + [Test] + public void Password_Encrypted_With_Salt() + { + IMember createdMember = null; + var memberType = MockedContentTypes.CreateSimpleMemberType(); + foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) + { + memberType.AddPropertyType(p.Value); + } + var mServiceMock = new Mock(); + mServiceMock.Setup(service => service.Exists("test")).Returns(false); + mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); + mServiceMock.Setup( + service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string u, string e, string p, string m) => + { + createdMember = new Member("test", e, u, p, memberType); + }) + .Returns(() => createdMember); + + var provider = new MembersMembershipProvider(mServiceMock.Object); + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); + MembershipCreateStatus status; + provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); + + Assert.AreNotEqual("test", createdMember.Password); + //Assert.AreNotEqual(provider.EncryptString("test"), createdMember.PasswordAnswer); + string salt; + var encodedPassword = provider.EncryptOrHashNewPassword("test", out salt); + Assert.AreEqual(encodedPassword, createdMember.Password); + } + + //[Test] + //public void Password_Hashed_With_Salt() + //{ + // IMember createdMember = null; + // var memberType = MockedContentTypes.CreateSimpleMemberType(); + // foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) + // { + // memberType.AddPropertyType(p.Value); + // } + // var mServiceMock = new Mock(); + // mServiceMock.Setup(service => service.Exists("test")).Returns(false); + // mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); + // mServiceMock.Setup( + // service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + // .Callback((string u, string e, string p, string m) => + // { + // createdMember = new Member("test", e, u, p, memberType); + // }) + // .Returns(() => createdMember); + + // var provider = new MembersMembershipProvider(mServiceMock.Object); + // provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" } }); + // MembershipCreateStatus status; + // provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); + + // Assert.AreNotEqual("test", createdMember.Password); + // Assert.AreNotEqual(provider.EncryptString("test"), createdMember.PasswordAnswer); + // string salt; + // var encodedPassword = provider.EncryptOrHashNewPassword("test", out salt); + // Assert.AreEqual(encodedPassword, createdMember.Password); + //} + + //[Test] + //public void Password_Encrypted_Validated_With_Salt() + + //[Test] + //public void Password_Encrypted_Validated_With_Salt() + } [TestFixture] public class MembershipProviderBaseTests { - //[Test] - //public void Change_Password_Base_Validation() - //[Test] - //public void ChangePasswordQuestionAndAnswer_Base_Validation() - //[Test] - //public void CreateUser_Base_Validation() + [Test] + public void Change_Password_Without_AllowManuallyChangingPassword_And_No_Pass_Validation() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.AllowManuallyChangingPassword).Returns(false); + var provider = providerMock.Object; - //[Test] - //public void GetPassword_Base_Validation() + Assert.Throws(() => provider.ChangePassword("test", "", "test")); + } - //[Test] - //public void ResetPassword_Base_Validation() + [Test] + public void Change_Password_With_AllowManuallyChangingPassword_And_Invalid_Creds() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.AllowManuallyChangingPassword).Returns(false); + providerMock.Setup(@base => @base.ValidateUser("test", "test")).Returns(false); + var provider = providerMock.Object; + + Assert.IsFalse(provider.ChangePassword("test", "test", "test")); + + } + + [Test] + public void ChangePasswordQuestionAndAnswer_Without_RequiresQuestionAndAnswer() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.RequiresQuestionAndAnswer).Returns(false); + var provider = providerMock.Object; + + Assert.Throws(() => provider.ChangePasswordQuestionAndAnswer("test", "test", "test", "test")); + } + + [Test] + public void ChangePasswordQuestionAndAnswer_Without_AllowManuallyChangingPassword_And_Invalid_Creds() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.RequiresQuestionAndAnswer).Returns(true); + providerMock.Setup(@base => @base.AllowManuallyChangingPassword).Returns(false); + providerMock.Setup(@base => @base.ValidateUser("test", "test")).Returns(false); + var provider = providerMock.Object; + + Assert.IsFalse(provider.ChangePasswordQuestionAndAnswer("test", "test", "test", "test")); + } + + [Test] + public void CreateUser_Not_Whitespace() + { + var providerMock = new Mock() {CallBase = true}; + var provider = providerMock.Object; + + MembershipCreateStatus status; + var result = provider.CreateUser("", "", "test@test.com", "", "", true, "", out status); + + Assert.IsNull(result); + Assert.AreEqual(MembershipCreateStatus.InvalidUserName, status); + } + + [Test] + public void CreateUser_Invalid_Question() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.RequiresQuestionAndAnswer).Returns(true); + var provider = providerMock.Object; + + MembershipCreateStatus status; + var result = provider.CreateUser("test", "test", "test@test.com", "", "", true, "", out status); + + Assert.IsNull(result); + Assert.AreEqual(MembershipCreateStatus.InvalidQuestion, status); + } + + [Test] + public void CreateUser_Invalid_Answer() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.RequiresQuestionAndAnswer).Returns(true); + var provider = providerMock.Object; + + MembershipCreateStatus status; + var result = provider.CreateUser("test", "test", "test@test.com", "test", "", true, "", out status); + + Assert.IsNull(result); + Assert.AreEqual(MembershipCreateStatus.InvalidAnswer, status); + } + + [Test] + public void GetPassword_Without_EnablePasswordRetrieval() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.EnablePasswordRetrieval).Returns(false); + var provider = providerMock.Object; + + Assert.Throws(() => provider.GetPassword("test", "test")); + } + + [Test] + public void GetPassword_With_Hashed() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.EnablePasswordRetrieval).Returns(true); + providerMock.Setup(@base => @base.PasswordFormat).Returns(MembershipPasswordFormat.Hashed); + var provider = providerMock.Object; + + Assert.Throws(() => provider.GetPassword("test", "test")); + } + + [Test] + public void ResetPassword_Without_EnablePasswordReset() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.EnablePasswordReset).Returns(false); + var provider = providerMock.Object; + + Assert.Throws(() => provider.ResetPassword("test", "test")); + } [Test] public void Sets_Defaults() { - var provider = new TestProvider(); + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; provider.Initialize("test", new NameValueCollection()); Assert.AreEqual("test", provider.Name); @@ -65,7 +282,8 @@ namespace Umbraco.Tests.Membership [Test] public void Throws_Exception_With_Hashed_Password_And_Password_Retrieval() { - var provider = new TestProvider(); + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; Assert.Throws(() => provider.Initialize("test", new NameValueCollection() { @@ -125,88 +343,5 @@ namespace Umbraco.Tests.Membership Assert.AreEqual(salt, initSalt); } - private class TestProvider : MembershipProviderBase - { - public override void UpdateUser(MembershipUser user) - { - throw new NotImplementedException(); - } - - public override bool ValidateUser(string username, string password) - { - throw new NotImplementedException(); - } - - public override bool UnlockUser(string userName) - { - throw new NotImplementedException(); - } - - public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) - { - throw new NotImplementedException(); - } - - public override MembershipUser GetUser(string username, bool userIsOnline) - { - throw new NotImplementedException(); - } - - public override string GetUserNameByEmail(string email) - { - throw new NotImplementedException(); - } - - public override bool DeleteUser(string username, bool deleteAllRelatedData) - { - throw new NotImplementedException(); - } - - public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) - { - throw new NotImplementedException(); - } - - public override int GetNumberOfUsersOnline() - { - throw new NotImplementedException(); - } - - public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) - { - throw new NotImplementedException(); - } - - public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) - { - throw new NotImplementedException(); - } - - protected override bool PerformChangePassword(string username, string oldPassword, string newPassword) - { - throw new NotImplementedException(); - } - - protected override bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) - { - throw new NotImplementedException(); - } - - protected override MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) - { - throw new NotImplementedException(); - } - - protected override string PerformGetPassword(string username, string answer) - { - throw new NotImplementedException(); - } - - protected override string PerformResetPassword(string username, string answer, string generatedPassword) - { - throw new NotImplementedException(); - } - } - } } diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index 5e263f76a4..e4fbda1d08 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; +using System.Web.Configuration; using System.Web.Hosting; using System.Web.Security; using Umbraco.Core; @@ -30,6 +31,15 @@ namespace Umbraco.Web.Security.Providers get { return _memberService ?? (_memberService = ApplicationContext.Current.Services.MemberService); } } + public MembersMembershipProvider() + { + } + + internal MembersMembershipProvider(IMemberService memberService) + { + _memberService = memberService; + } + public string ProviderName { get { return "MembersMembershipProvider"; } @@ -80,7 +90,7 @@ namespace Umbraco.Web.Security.Providers // This is allowed based on the overridden AllowManuallyChangingPassword option. // in order to support updating passwords from the umbraco core, we can't validate the old password - var m = _memberService.GetByUsername(username); + var m = MemberService.GetByUsername(username); if (m == null) return false; string salt; @@ -88,8 +98,8 @@ namespace Umbraco.Web.Security.Providers m.Password = FormatPasswordForStorage(encodedPassword, salt); m.LastPasswordChangeDate = DateTime.Now; - - _memberService.Save(m); + + MemberService.Save(m); return true; } @@ -113,7 +123,7 @@ namespace Umbraco.Web.Security.Providers } member.PasswordQuestion = newPasswordQuestion; - member.PasswordAnswer = newPasswordAnswer; + member.PasswordAnswer = EncryptString(newPasswordAnswer); MemberService.Save(member); @@ -158,10 +168,14 @@ namespace Umbraco.Web.Security.Providers string salt; var encodedPassword = EncryptOrHashNewPassword(password, out salt); - var member = MemberService.CreateMember(email, username, encodedPassword, memberTypeAlias); + var member = MemberService.CreateMember( + email, + username, + FormatPasswordForStorage(encodedPassword, salt), + memberTypeAlias); member.PasswordQuestion = passwordQuestion; - member.PasswordAnswer = passwordAnswer; + member.PasswordAnswer = EncryptString(passwordAnswer); member.IsApproved = isApproved; member.LastLoginDate = DateTime.Now; member.LastPasswordChangeDate = DateTime.Now; @@ -289,9 +303,9 @@ namespace Umbraco.Web.Security.Providers throw new ProviderException("The supplied user is not found"); } - //TODO: We need to encrypt the answer here to match against the encrypted answer in the database + var encAnswer = EncryptString(answer); - if (RequiresQuestionAndAnswer && m.PasswordAnswer != answer) + if (RequiresQuestionAndAnswer && m.PasswordAnswer != encAnswer) { throw new ProviderException("Incorrect password answer"); } @@ -301,6 +315,15 @@ namespace Umbraco.Web.Security.Providers return decodedPassword; } + internal string EncryptString(string str) + { + var bytes = Encoding.Unicode.GetBytes(str); + var password = new byte[bytes.Length]; + Buffer.BlockCopy(bytes, 0, password, 0, bytes.Length); + var encBytes = EncryptPassword(password, MembershipPasswordCompatibilityMode.Framework40); + return Convert.ToBase64String(encBytes); + } + /// /// Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user. /// @@ -395,16 +418,16 @@ namespace Umbraco.Web.Security.Providers throw new ProviderException("The member is locked out."); } - //TODO: We need to encrypt the answer here to match against the encrypted answer in the database + var encAnswer = EncryptString(answer); - if (RequiresQuestionAndAnswer && m.PasswordAnswer != answer) + if (RequiresQuestionAndAnswer && m.PasswordAnswer != encAnswer) { throw new ProviderException("Incorrect password answer"); } string salt; var encodedPassword = EncryptOrHashNewPassword(generatedPassword, out salt); - m.Password = encodedPassword; + m.Password = FormatPasswordForStorage(encodedPassword, salt); m.LastPasswordChangeDate = DateTime.Now; MemberService.Save(m); From fbdb1d5d6cf612d5d0bf1d4417e1f2608196e11f Mon Sep 17 00:00:00 2001 From: Shannon Date: Sat, 28 Dec 2013 14:01:08 +1100 Subject: [PATCH 21/27] Fixes: U4-3942 Cannot configure the request if there is not content assigned - exception Conflicts: src/Umbraco.Tests/Umbraco.Tests.csproj --- .../PublishedContentRequestEngineTests.cs | 110 ++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../Routing/PublishedContentRequestEngine.cs | 32 +++-- 3 files changed, 132 insertions(+), 11 deletions(-) create mode 100644 src/Umbraco.Tests/PublishedContent/PublishedContentRequestEngineTests.cs diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentRequestEngineTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentRequestEngineTests.cs new file mode 100644 index 0000000000..5bfc94fd05 --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentRequestEngineTests.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections; +using System.Collections.ObjectModel; +using System.Globalization; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web.Routing; + +namespace Umbraco.Tests.PublishedContent +{ + [TestFixture] + public class PublishedContentRequestEngineTests : BaseRoutingTest + { + + protected override DatabaseBehavior DatabaseTestBehavior + { + get { return DatabaseBehavior.NoDatabasePerFixture; } + } + + [Test] + public void Ctor_Throws_On_Null_PCR() + { + Assert.Throws(() => new PublishedContentRequestEngine(null)); + } + + [Test] + public void ConfigureRequest_Returns_False_Without_HasPublishedContent() + { + var routeCtx = GetRoutingContext("/test"); + + var pcre = new PublishedContentRequestEngine( + new PublishedContentRequest( + routeCtx.UmbracoContext.CleanedUmbracoUrl, routeCtx)); + + var result = pcre.ConfigureRequest(); + Assert.IsFalse(result); + } + + [Test] + public void ConfigureRequest_Returns_False_When_IsRedirect() + { + var routeCtx = GetRoutingContext("/test"); + + var pcr = new PublishedContentRequest(routeCtx.UmbracoContext.CleanedUmbracoUrl, routeCtx); + var pc = GetPublishedContentMock(); + pcr.PublishedContent = pc.Object; + pcr.Culture = new CultureInfo("en-AU"); + pcr.SetRedirect("/hello"); + var pcre = new PublishedContentRequestEngine(pcr); + + var result = pcre.ConfigureRequest(); + Assert.IsFalse(result); + } + + [Test] + public void ConfigureRequest_Adds_HttpContext_Items_When_Published_Content_Assigned() + { + var routeCtx = GetRoutingContext("/test"); + + var pcr = new PublishedContentRequest(routeCtx.UmbracoContext.CleanedUmbracoUrl, routeCtx); + var pc = GetPublishedContentMock(); + pcr.PublishedContent = pc.Object; + pcr.Culture = new CultureInfo("en-AU"); + var pcre = new PublishedContentRequestEngine(pcr); + + pcre.ConfigureRequest(); + + Assert.AreEqual(1, routeCtx.UmbracoContext.HttpContext.Items["pageID"]); + Assert.AreEqual(pcr.UmbracoPage.Elements.Count, ((Hashtable)routeCtx.UmbracoContext.HttpContext.Items["pageElements"]).Count); + } + + [Test] + public void ConfigureRequest_Sets_UmbracoPage_When_Published_Content_Assigned() + { + var routeCtx = GetRoutingContext("/test"); + + var pcr = new PublishedContentRequest(routeCtx.UmbracoContext.CleanedUmbracoUrl, routeCtx); + var pc = GetPublishedContentMock(); + pcr.Culture = new CultureInfo("en-AU"); + pcr.PublishedContent = pc.Object; + var pcre = new PublishedContentRequestEngine(pcr); + + pcre.ConfigureRequest(); + + Assert.IsNotNull(pcr.UmbracoPage); + } + + private Mock GetPublishedContentMock() + { + var pc = new Mock(); + pc.Setup(content => content.Id).Returns(1); + pc.Setup(content => content.Name).Returns("test"); + pc.Setup(content => content.DocumentTypeId).Returns(2); + pc.Setup(content => content.DocumentTypeAlias).Returns("testAlias"); + pc.Setup(content => content.WriterName).Returns("admin"); + pc.Setup(content => content.CreatorName).Returns("admin"); + pc.Setup(content => content.CreateDate).Returns(DateTime.Now); + pc.Setup(content => content.UpdateDate).Returns(DateTime.Now); + pc.Setup(content => content.Path).Returns("-1,1"); + pc.Setup(content => content.Version).Returns(Guid.NewGuid); + pc.Setup(content => content.Parent).Returns(() => null); + pc.Setup(content => content.Version).Returns(Guid.NewGuid); + pc.Setup(content => content.Properties).Returns(new Collection()); + return pc; + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 22048799ac..b3dc588345 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -213,6 +213,7 @@ + diff --git a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs index 45de9c2f8b..40fa86b67b 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs @@ -46,7 +46,10 @@ namespace Umbraco.Web.Routing /// /// Prepares the request. /// - public void PrepareRequest() + /// + /// Returns false if the request was not successfully prepared + /// + public bool PrepareRequest() { // note - at that point the original legacy module did something do handle IIS custom 404 errors // ie pages looking like /anything.aspx?404;/path/to/document - I guess the reason was to support @@ -62,10 +65,12 @@ namespace Umbraco.Web.Routing // if request has been flagged to redirect then return // whoever called us is in charge of actually redirecting - if (_pcr.IsRedirect) - return; + if (_pcr.IsRedirect) + { + return false; + } - // set the culture on the thread - once, so it's set when running document lookups + // set the culture on the thread - once, so it's set when running document lookups Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = _pcr.Culture; // find the document & template @@ -85,24 +90,25 @@ namespace Umbraco.Web.Routing // to find out the appropriate template //complete the PCR and assign the remaining values - ConfigureRequest(); + return ConfigureRequest(); } /// /// Called by PrepareRequest once everything has been discovered, resolved and assigned to the PCR. This method /// finalizes the PCR with the values assigned. /// + /// + /// Returns false if the request was not successfully configured + /// /// /// This method logic has been put into it's own method in case developers have created a custom PCR or are assigning their own values /// but need to finalize it themselves. - /// - /// This method will throw an exception if no content has been assigned /// - public void ConfigureRequest() + public bool ConfigureRequest() { if (_pcr.HasPublishedContent == false) { - throw new InvalidOperationException("Cannot configure the request if there is not content assigned"); + return false; } // set the culture on the thread -- again, 'cos it might have changed in the event handler @@ -110,8 +116,10 @@ namespace Umbraco.Web.Routing // if request has been flagged to redirect, or has no content to display, // then return - whoever called us is in charge of actually redirecting - if (_pcr.IsRedirect || !_pcr.HasPublishedContent) - return; + if (_pcr.IsRedirect || _pcr.HasPublishedContent == false) + { + return false; + } // we may be 404 _and_ have a content @@ -132,6 +140,8 @@ namespace Umbraco.Web.Routing // used by many legacy objects _routingContext.UmbracoContext.HttpContext.Items["pageID"] = _pcr.PublishedContent.Id; _routingContext.UmbracoContext.HttpContext.Items["pageElements"] = _pcr.UmbracoPage.Elements; + + return true; } /// From 8a70c440f26ddd4c8b29412191bafb336e2d8d6c Mon Sep 17 00:00:00 2001 From: Shannon Date: Sat, 28 Dec 2013 18:55:43 +1100 Subject: [PATCH 22/27] Added many more unit tests for the membership provider base class and fixed some logic inconsistencies. --- .../NameValueCollectionExtensions.cs | 36 +- .../Security/MembershipProviderBase.cs | 105 ++++-- .../MembersMembershipProviderTests.cs | 129 +++++++ .../Membership/MembershipProviderBaseTests.cs | 318 +++++++++++------- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../Providers/MembersMembershipProvider.cs | 7 +- src/umbraco.businesslogic/User.cs | 3 + .../businesslogic/member/Member.cs | 4 +- .../UsersMembershipProvider.cs | 29 +- .../members/UmbracoMembershipProvider.cs | 35 +- 10 files changed, 422 insertions(+), 245 deletions(-) create mode 100644 src/Umbraco.Tests/Membership/MembersMembershipProviderTests.cs diff --git a/src/Umbraco.Core/NameValueCollectionExtensions.cs b/src/Umbraco.Core/NameValueCollectionExtensions.cs index 0bfa485438..d47add61ed 100644 --- a/src/Umbraco.Core/NameValueCollectionExtensions.cs +++ b/src/Umbraco.Core/NameValueCollectionExtensions.cs @@ -13,23 +13,23 @@ namespace Umbraco.Core { return collection.Keys.Cast().Any(k => (string) k == key); } - - public static T GetValue(this NameValueCollection collection, string key, T defaultIfNotFound) - { - if (collection.ContainsKey(key) == false) - { - return defaultIfNotFound; - } - - var val = collection[key]; - if (val == null) - { - return defaultIfNotFound; - } - - var result = val.TryConvertTo(); - - return result.Success ? result.Result : defaultIfNotFound; - } + + public static T GetValue(this NameValueCollection collection, string key, T defaultIfNotFound) + { + if (collection.ContainsKey(key) == false) + { + return defaultIfNotFound; + } + + var val = collection[key]; + if (val == null) + { + return defaultIfNotFound; + } + + var result = val.TryConvertTo(); + + return result.Success ? result.Result : defaultIfNotFound; + } } } diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs index 3794327781..5480b8220c 100644 --- a/src/Umbraco.Core/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs @@ -61,7 +61,7 @@ namespace Umbraco.Core.Security private string _passwordStrengthRegularExpression; private bool _requiresQuestionAndAnswer; private bool _requiresUniqueEmail; - + private string _customHashAlgorithmType ; internal bool UseLegacyEncoding; #region Properties @@ -251,7 +251,8 @@ namespace Umbraco.Core.Security LogHelper.Error("Cannot specify a Hashed password format with the enabledPasswordRetrieval option set to true", ex); throw ex; } - + + _customHashAlgorithmType = config.GetValue("hashAlgorithmType", string.Empty); } /// @@ -582,15 +583,26 @@ namespace Umbraco.Core.Security return num; } - protected string FormatPasswordForStorage(string pass, string salt) + /// + /// If the password format is a hashed keyed algorithm then we will pre-pend the salt used to hash the password + /// to the hashed password itself. + /// + /// + /// + /// + protected internal string FormatPasswordForStorage(string pass, string salt) { if (UseLegacyEncoding) { return pass; } - - //the better way, we use salt per member - return salt + pass; + + if (PasswordFormat == MembershipPasswordFormat.Hashed) + { + //the better way, we use salt per member + return salt + pass; + } + return pass; } protected bool IsEmailValid(string email) @@ -602,7 +614,7 @@ namespace Umbraco.Core.Security return Regex.IsMatch(email, pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); } - protected string EncryptOrHashPassword(string pass, string salt) + protected internal string EncryptOrHashPassword(string pass, string salt) { //if we are doing it the old way @@ -613,12 +625,13 @@ namespace Umbraco.Core.Security //This is the correct way to implement this (as per the sql membership provider) - if ((int)PasswordFormat == 0) + if (PasswordFormat == MembershipPasswordFormat.Clear) return pass; var bytes = Encoding.Unicode.GetBytes(pass); var numArray1 = Convert.FromBase64String(salt); byte[] inArray; - if ((int)PasswordFormat == 1) + + if (PasswordFormat == MembershipPasswordFormat.Hashed) { var hashAlgorithm = GetHashAlgorithm(pass); var algorithm = hashAlgorithm as KeyedHashAlgorithm; @@ -657,6 +670,8 @@ namespace Umbraco.Core.Security } else { + //this code is copied from the sql membership provider - pretty sure this could be nicely re-written to completely + // ignore the salt stuff since we are not salting the password when encrypting. var password = new byte[numArray1.Length + bytes.Length]; Buffer.BlockCopy(numArray1, 0, password, 0, numArray1.Length); Buffer.BlockCopy(bytes, 0, password, numArray1.Length, bytes.Length); @@ -665,6 +680,31 @@ namespace Umbraco.Core.Security return Convert.ToBase64String(inArray); } + /// + /// Checks the password. + /// + /// The password. + /// The dbPassword. + /// + protected internal bool CheckPassword(string password, string dbPassword) + { + switch (PasswordFormat) + { + case MembershipPasswordFormat.Encrypted: + var decrypted = DecryptPassword(dbPassword); + return decrypted == password; + case MembershipPasswordFormat.Hashed: + string salt; + var storedHashedPass = StoredPassword(dbPassword, out salt); + var hashed = EncryptOrHashPassword(password, salt); + return storedHashedPass == hashed; + case MembershipPasswordFormat.Clear: + return password == dbPassword; + default: + throw new ArgumentOutOfRangeException(); + } + } + /// /// Encrypt/hash a new password with a new salt /// @@ -677,26 +717,7 @@ namespace Umbraco.Core.Security return EncryptOrHashPassword(newPassword, salt); } - /// - /// Gets the encrypted or hashed string of an existing password for an existing user - /// - /// The stored string for the password - /// - protected string EncryptOrHashExistingPassword(string storedPassword) - { - if (UseLegacyEncoding) - { - return EncryptOrHashPassword(storedPassword, storedPassword); - } - else - { - string salt; - var pass = StoredPassword(storedPassword, PasswordFormat, out salt); - return EncryptOrHashPassword(pass, salt); - } - } - - protected string DecodePassword(string pass) + protected internal string DecryptPassword(string pass) { //if we are doing it the old way @@ -712,7 +733,7 @@ namespace Umbraco.Core.Security case 0: return pass; case 1: - throw new ProviderException("Provider can not decode hashed password"); + throw new ProviderException("Provider can not decrypt hashed password"); default: var bytes = DecryptPassword(Convert.FromBase64String(pass)); return bytes == null ? null : Encoding.Unicode.GetString(bytes, 16, bytes.Length - 16); @@ -723,17 +744,16 @@ namespace Umbraco.Core.Security /// Returns the hashed password without the salt if it is hashed /// /// - /// /// returns the salt /// - internal static string StoredPassword(string storedString, MembershipPasswordFormat format, out string salt) + internal string StoredPassword(string storedString, out string salt) { - switch (format) + switch (PasswordFormat) { case MembershipPasswordFormat.Hashed: var saltLen = GenerateSalt(); salt = storedString.Substring(0, saltLen.Length); - return storedString.Substring(saltLen.Length); + return storedString.Substring(saltLen.Length); case MembershipPasswordFormat.Clear: case MembershipPasswordFormat.Encrypted: default: @@ -750,7 +770,7 @@ namespace Umbraco.Core.Security return Convert.ToBase64String(numArray); } - protected HashAlgorithm GetHashAlgorithm(string password) + protected internal HashAlgorithm GetHashAlgorithm(string password) { if (UseLegacyEncoding) { @@ -765,10 +785,21 @@ namespace Umbraco.Core.Security }; } } - + //get the algorithm by name - return HashAlgorithm.Create(Membership.HashAlgorithmType); + if (_customHashAlgorithmType.IsNullOrWhiteSpace()) + { + _customHashAlgorithmType = Membership.HashAlgorithmType; + } + + var alg = HashAlgorithm.Create(_customHashAlgorithmType); + if (alg == null) + { + throw new InvalidOperationException("The hash algorithm specified " + Membership.HashAlgorithmType + " cannot be resolved"); + } + + return alg; } /// diff --git a/src/Umbraco.Tests/Membership/MembersMembershipProviderTests.cs b/src/Umbraco.Tests/Membership/MembersMembershipProviderTests.cs new file mode 100644 index 0000000000..411c22f750 --- /dev/null +++ b/src/Umbraco.Tests/Membership/MembersMembershipProviderTests.cs @@ -0,0 +1,129 @@ +using System.Collections.Specialized; +using System.Web.Security; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers.Entities; +using Umbraco.Web.Security.Providers; + +namespace Umbraco.Tests.Membership +{ + [TestFixture] + public class MembersMembershipProviderTests + { + //[Test] + //public void Set_Default_Member_Type_On_Init() + + //[Test] + //public void Create_User_Already_Exists() + //{ + + //} + + //[Test] + //public void Create_User_Requires_Unique_Email() + //{ + + //} + + [Test] + public void Answer_Is_Encrypted() + { + IMember createdMember = null; + var memberType = MockedContentTypes.CreateSimpleMemberType(); + foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) + { + memberType.AddPropertyType(p.Value); + } + var mServiceMock = new Mock(); + mServiceMock.Setup(service => service.Exists("test")).Returns(false); + mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); + mServiceMock.Setup( + service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string u, string e, string p, string m) => + { + createdMember = new Member("test", e, u, p, memberType); + }) + .Returns(() => createdMember); + var provider = new MembersMembershipProvider(mServiceMock.Object); + + MembershipCreateStatus status; + provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); + + Assert.AreNotEqual("test", createdMember.PasswordAnswer); + Assert.AreEqual(provider.EncryptString("test"), createdMember.PasswordAnswer); + } + + [Test] + public void Password_Encrypted() + { + IMember createdMember = null; + var memberType = MockedContentTypes.CreateSimpleMemberType(); + foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) + { + memberType.AddPropertyType(p.Value); + } + var mServiceMock = new Mock(); + mServiceMock.Setup(service => service.Exists("test")).Returns(false); + mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); + mServiceMock.Setup( + service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string u, string e, string p, string m) => + { + createdMember = new Member("test", e, u, p, memberType); + }) + .Returns(() => createdMember); + + var provider = new MembersMembershipProvider(mServiceMock.Object); + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); + MembershipCreateStatus status; + provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); + + Assert.AreNotEqual("test", createdMember.Password); + var decrypted = provider.DecryptPassword(createdMember.Password); + Assert.AreEqual("test", decrypted); + } + + [Test] + public void Password_Hashed_With_Salt() + { + IMember createdMember = null; + var memberType = MockedContentTypes.CreateSimpleMemberType(); + foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) + { + memberType.AddPropertyType(p.Value); + } + var mServiceMock = new Mock(); + mServiceMock.Setup(service => service.Exists("test")).Returns(false); + mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); + mServiceMock.Setup( + service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string u, string e, string p, string m) => + { + createdMember = new Member("test", e, u, p, memberType); + }) + .Returns(() => createdMember); + + var provider = new MembersMembershipProvider(mServiceMock.Object); + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" }, { "hashAlgorithmType", "HMACSHA256" } }); + MembershipCreateStatus status; + provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); + + Assert.AreNotEqual("test", createdMember.Password); + + string salt; + var storedPassword = provider.StoredPassword(createdMember.Password, out salt); + var hashedPassword = provider.EncryptOrHashPassword("test", salt); + Assert.AreEqual(hashedPassword, storedPassword); + } + + //[Test] + //public void Password_Encrypted_Validated_With_Salt() + + //[Test] + //public void Password_Encrypted_Validated_With_Salt() + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs index 2af97b49e2..defa6fb4cd 100644 --- a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs +++ b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs @@ -4,138 +4,16 @@ using System.Collections.Specialized; using System.Configuration.Provider; using System.Diagnostics; using System.Linq; +using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using System.Web.Security; using Moq; using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Models; using Umbraco.Core.Security; -using Umbraco.Core.Services; -using Umbraco.Tests.TestHelpers.Entities; -using Umbraco.Web.Security.Providers; namespace Umbraco.Tests.Membership { - [TestFixture] - public class MembersMembershipProviderTests - { - //[Test] - //public void Set_Default_Member_Type_On_Init() - - //[Test] - //public void Create_User_Already_Exists() - //{ - - //} - - //[Test] - //public void Create_User_Requires_Unique_Email() - //{ - - //} - - [Test] - public void Answer_Is_Encrypted() - { - IMember createdMember = null; - var memberType = MockedContentTypes.CreateSimpleMemberType(); - foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) - { - memberType.AddPropertyType(p.Value); - } - var mServiceMock = new Mock(); - mServiceMock.Setup(service => service.Exists("test")).Returns(false); - mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); - mServiceMock.Setup( - service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((string u, string e, string p, string m) => - { - createdMember = new Member("test", e, u, p, memberType); - }) - .Returns(() => createdMember); - var provider = new MembersMembershipProvider(mServiceMock.Object); - - MembershipCreateStatus status; - provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); - - Assert.AreNotEqual("test", createdMember.PasswordAnswer); - Assert.AreEqual(provider.EncryptString("test"), createdMember.PasswordAnswer); - } - - [Test] - public void Password_Encrypted_With_Salt() - { - IMember createdMember = null; - var memberType = MockedContentTypes.CreateSimpleMemberType(); - foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) - { - memberType.AddPropertyType(p.Value); - } - var mServiceMock = new Mock(); - mServiceMock.Setup(service => service.Exists("test")).Returns(false); - mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); - mServiceMock.Setup( - service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((string u, string e, string p, string m) => - { - createdMember = new Member("test", e, u, p, memberType); - }) - .Returns(() => createdMember); - - var provider = new MembersMembershipProvider(mServiceMock.Object); - provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); - MembershipCreateStatus status; - provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); - - Assert.AreNotEqual("test", createdMember.Password); - //Assert.AreNotEqual(provider.EncryptString("test"), createdMember.PasswordAnswer); - string salt; - var encodedPassword = provider.EncryptOrHashNewPassword("test", out salt); - Assert.AreEqual(encodedPassword, createdMember.Password); - } - - //[Test] - //public void Password_Hashed_With_Salt() - //{ - // IMember createdMember = null; - // var memberType = MockedContentTypes.CreateSimpleMemberType(); - // foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) - // { - // memberType.AddPropertyType(p.Value); - // } - // var mServiceMock = new Mock(); - // mServiceMock.Setup(service => service.Exists("test")).Returns(false); - // mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); - // mServiceMock.Setup( - // service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - // .Callback((string u, string e, string p, string m) => - // { - // createdMember = new Member("test", e, u, p, memberType); - // }) - // .Returns(() => createdMember); - - // var provider = new MembersMembershipProvider(mServiceMock.Object); - // provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" } }); - // MembershipCreateStatus status; - // provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); - - // Assert.AreNotEqual("test", createdMember.Password); - // Assert.AreNotEqual(provider.EncryptString("test"), createdMember.PasswordAnswer); - // string salt; - // var encodedPassword = provider.EncryptOrHashNewPassword("test", out salt); - // Assert.AreEqual(encodedPassword, createdMember.Password); - //} - - //[Test] - //public void Password_Encrypted_Validated_With_Salt() - - //[Test] - //public void Password_Encrypted_Validated_With_Salt() - - } - [TestFixture] public class MembershipProviderBaseTests { @@ -330,18 +208,204 @@ namespace Umbraco.Tests.Membership lastLength = result.Length; } } - + [Test] - public void Get_StoredPassword() + public void Get_Stored_Password_Hashed() { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" }, { "hashAlgorithmType", "HMACSHA256" } }); + var salt = MembershipProviderBase.GenerateSalt(); var stored = salt + "ThisIsAHashedPassword"; string initSalt; - var result = MembershipProviderBase.StoredPassword(stored, MembershipPasswordFormat.Hashed, out initSalt); + var result = provider.StoredPassword(stored, out initSalt); - Assert.AreEqual(salt, initSalt); + Assert.AreEqual("ThisIsAHashedPassword", result); } + [Test] + public void Get_Stored_Password_Encrypted() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); + + var stored = "ThisIsAnEncryptedPassword"; + + string initSalt; + var result = provider.StoredPassword(stored, out initSalt); + + Assert.AreEqual("ThisIsAnEncryptedPassword", result); + } + + [Test] + public void Get_Stored_Password_Clear() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Clear" } }); + + var salt = MembershipProviderBase.GenerateSalt(); + var stored = "ThisIsAClearPassword"; + + string initSalt; + var result = provider.StoredPassword(stored, out initSalt); + + Assert.AreEqual("ThisIsAClearPassword", result); + } + + [Test] + public void Format_Pass_For_Storage_Hashed() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" }, { "hashAlgorithmType", "HMACSHA256" } }); + + var salt = MembershipProviderBase.GenerateSalt(); + var stored = "ThisIsAHashedPassword"; + + var result = provider.FormatPasswordForStorage(stored, salt); + + Assert.AreEqual(salt + "ThisIsAHashedPassword", result); + } + + [Test] + public void Format_Pass_For_Storage_Encrypted() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); + + var salt = MembershipProviderBase.GenerateSalt(); + var stored = "ThisIsAnEncryptedPassword"; + + var result = provider.FormatPasswordForStorage(stored, salt); + + Assert.AreEqual("ThisIsAnEncryptedPassword", result); + } + + [Test] + public void Format_Pass_For_Storage_Clear() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Clear" } }); + + var salt = MembershipProviderBase.GenerateSalt(); + var stored = "ThisIsAClearPassword"; + + var result = provider.FormatPasswordForStorage(stored, salt); + + Assert.AreEqual("ThisIsAClearPassword", result); + } + + [Test] + public void Check_Password_Hashed_KeyedHashAlgorithm() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" }, { "hashAlgorithmType", "HMACSHA256" } }); + + string salt; + var pass = "ThisIsAHashedPassword"; + var hashed = provider.EncryptOrHashNewPassword(pass, out salt); + var storedPassword = provider.FormatPasswordForStorage(hashed, salt); + + var result = provider.CheckPassword("ThisIsAHashedPassword", storedPassword); + + Assert.IsTrue(result); + } + + [Test] + public void Check_Password_Hashed_Non_KeyedHashAlgorithm() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" } }); + + string salt; + var pass = "ThisIsAHashedPassword"; + var hashed = provider.EncryptOrHashNewPassword(pass, out salt); + var storedPassword = provider.FormatPasswordForStorage(hashed, salt); + + var result = provider.CheckPassword("ThisIsAHashedPassword", storedPassword); + + Assert.IsTrue(result); + } + + [Test] + public void Check_Password_Encrypted() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); + + string salt; + var pass = "ThisIsAnEncryptedPassword"; + var encrypted = provider.EncryptOrHashNewPassword(pass, out salt); + + var result = provider.CheckPassword("ThisIsAnEncryptedPassword", encrypted); + + Assert.IsTrue(result); + } + + [Test] + public void Check_Password_Clear() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Clear" } }); + + var pass = "ThisIsAClearPassword"; + + var result = provider.CheckPassword("ThisIsAClearPassword", pass); + + Assert.IsTrue(result); + } + + [Test] + public void Can_Decrypt_Password() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); + + string salt; + var pass = "ThisIsAnEncryptedPassword"; + var encrypted = provider.EncryptOrHashNewPassword(pass, out salt); + + var result = provider.DecryptPassword(encrypted); + + Assert.AreEqual(pass, result); + + } + + [Test] + public void Get_Hash_Algorithm_Legacy() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { {"useLegacyEncoding", "true"}, { "passwordFormat", "Hashed" }, { "hashAlgorithmType", "HMACSHA256" } }); + + var alg = provider.GetHashAlgorithm("blah"); + + Assert.IsTrue(alg is HMACSHA1); + } + + [Test] + public void Get_Hash_Algorithm() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" }, { "hashAlgorithmType", "HMACSHA256" } }); + + var alg = provider.GetHashAlgorithm("blah"); + + Assert.IsTrue(alg is HMACSHA256); + } + + } } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index b3dc588345..6a3ded0fde 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -155,6 +155,7 @@ + diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index e4fbda1d08..a735e3a9a2 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -310,7 +310,7 @@ namespace Umbraco.Web.Security.Providers throw new ProviderException("Incorrect password answer"); } - var decodedPassword = DecodePassword(m.Password); + var decodedPassword = DecryptPassword(m.Password); return decodedPassword; } @@ -511,10 +511,7 @@ namespace Umbraco.Web.Security.Providers return false; } - string salt; - var encodedPassword = EncryptOrHashNewPassword(password, out salt); - - var authenticated = (encodedPassword == member.Password); + var authenticated = CheckPassword(password, member.Password); if (authenticated == false) { diff --git a/src/umbraco.businesslogic/User.cs b/src/umbraco.businesslogic/User.cs index 5da83d2a6d..9584b2f1cd 100644 --- a/src/umbraco.businesslogic/User.cs +++ b/src/umbraco.businesslogic/User.cs @@ -212,6 +212,7 @@ namespace umbraco.BusinessLogic return UserType.Alias == "admin"; } + [Obsolete("Do not use this method to validate credentials, use the user's membership provider to do authentication. This method will not work if the password format is 'Encrypted'")] public bool ValidatePassword(string password) { string userLogin = @@ -310,6 +311,7 @@ namespace umbraco.BusinessLogic /// The login name. /// The password. /// + [Obsolete("Do not use this method to validate credentials, use the user's membership provider to do authentication. This method will not work if the password format is 'Encrypted'")] public static bool validateCredentials(string lname, string passw) { return validateCredentials(lname, passw, true); @@ -322,6 +324,7 @@ namespace umbraco.BusinessLogic /// The password. /// if set to true [check for umbraco console access]. /// + [Obsolete("Do not use this method to validate credentials, use the user's membership provider to do authentication. This method will not work if the password format is 'Encrypted'")] public static bool validateCredentials(string lname, string passw, bool checkForUmbracoConsoleAccess) { string consoleCheckSql = ""; diff --git a/src/umbraco.cms/businesslogic/member/Member.cs b/src/umbraco.cms/businesslogic/member/Member.cs index 9b051aea7b..40275672d1 100644 --- a/src/umbraco.cms/businesslogic/member/Member.cs +++ b/src/umbraco.cms/businesslogic/member/Member.cs @@ -353,7 +353,6 @@ namespace umbraco.cms.businesslogic.member /// Member login /// Member password /// The member with the credentials - null if none exists - public static Member GetMemberFromLoginNameAndPassword(string loginName, string password) { if (IsMember(loginName)) @@ -375,7 +374,8 @@ namespace umbraco.cms.businesslogic.member return null; } } - + + [Obsolete("This method will not work if the password format is encrypted since the encryption that is performed is not static and a new value will be created each time the same string is encrypted")] public static Member GetMemberFromLoginAndEncodedPassword(string loginName, string password) { var o = SqlHelper.ExecuteScalar( diff --git a/src/umbraco.providers/UsersMembershipProvider.cs b/src/umbraco.providers/UsersMembershipProvider.cs index 64534abf3a..2f625095ad 100644 --- a/src/umbraco.providers/UsersMembershipProvider.cs +++ b/src/umbraco.providers/UsersMembershipProvider.cs @@ -460,7 +460,7 @@ namespace umbraco.providers return true; } - return user.ValidatePassword(EncryptOrHashExistingPassword(password)); + return CheckPassword(password, user.Password); } } return false; @@ -468,32 +468,7 @@ namespace umbraco.providers #endregion #region Helper Methods - /// - /// Checks the password. - /// - /// The password. - /// The dbPassword. - /// - internal bool CheckPassword(string password, string dbPassword) - { - string pass1 = password; - string pass2 = dbPassword; - - switch (PasswordFormat) - { - case MembershipPasswordFormat.Encrypted: - pass2 = DecodePassword(dbPassword); - break; - case MembershipPasswordFormat.Hashed: - pass1 = EncryptOrHashExistingPassword(password); - break; - default: - break; - } - return (pass1 == pass2) ? true : false; - } - - + /// /// Encodes the password. /// diff --git a/src/umbraco.providers/members/UmbracoMembershipProvider.cs b/src/umbraco.providers/members/UmbracoMembershipProvider.cs index fa3a70804e..cf4714496d 100644 --- a/src/umbraco.providers/members/UmbracoMembershipProvider.cs +++ b/src/umbraco.providers/members/UmbracoMembershipProvider.cs @@ -452,7 +452,7 @@ namespace umbraco.providers.members } } - var decodedPassword = DecodePassword(m.GetPassword()); + var decodedPassword = DecryptPassword(m.GetPassword()); return decodedPassword; } @@ -667,8 +667,11 @@ namespace umbraco.providers.members /// public override bool ValidateUser(string username, string password) { - var m = Member.GetMemberFromLoginAndEncodedPassword(username, EncryptOrHashExistingPassword(password)); - if (m != null) + var m = Member.GetMemberFromLoginName(username); + if (m == null) return false; + var authenticated = CheckPassword(password, m.GetPassword()); + + if (authenticated) { // check for lock status. If locked, then set the member property to null if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) @@ -824,32 +827,6 @@ namespace umbraco.providers.members #region Helper Methods - /// - /// Checks the password. - /// - /// The password. - /// The dbPassword. - /// - internal bool CheckPassword(string password, string dbPassword) - { - string pass1 = password; - string pass2 = dbPassword; - - switch (PasswordFormat) - { - case MembershipPasswordFormat.Encrypted: - pass2 = DecodePassword(dbPassword); - break; - case MembershipPasswordFormat.Hashed: - pass1 = EncryptOrHashExistingPassword(password); - break; - default: - break; - } - return (pass1 == pass2) ? true : false; - } - - /// /// Encodes the password. /// From 4d3f67885a058120f34988b7338cf30f969c08f1 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sun, 29 Dec 2013 14:12:23 +0100 Subject: [PATCH 23/27] Fixes U4-1076 Apostrophes in document type description are shown preceded/escaped by a slash in the Create Node dialog --- .../umbraco.presentation/umbraco/create/content.ascx.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/content.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/content.ascx.cs index 91b2a8c519..7b5ade8b0e 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/content.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/content.ascx.cs @@ -41,7 +41,8 @@ namespace umbraco.cms.presentation.create.controls { string docDescription = "No description available..."; if (string.IsNullOrEmpty(dt.Description) == false) - docDescription = dt.Description; + docDescription = System.Web.HttpUtility.HtmlEncode(dt.Description); + docDescription = "" + dt.Text + "
    " + docDescription.Replace(Environment.NewLine, "
    "); docDescription = docDescription.Replace("'", "\\'"); From 32bc2f1d69d292e1a79466529558013119414c3e Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sun, 29 Dec 2013 14:34:54 +0100 Subject: [PATCH 24/27] merge ismailmayat-UK-Festival --- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 1 + .../DataTypes/LegacyUploadFieldWorkaround.cs | 2 +- .../umbraco/controls/ContentControl.cs | 340 ++++++++++-------- .../businesslogic/Property/Property.cs | 18 +- .../businesslogic/datatype/DefaultData.cs | 7 +- 5 files changed, 206 insertions(+), 162 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index c0f093b70b..9cd5f47a95 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -279,6 +279,7 @@ You cannot split a cell that hasn't been merged. Error in XSLT source The XSLT has not been saved, because it contained error(s) + There is a configuration error with the data type used for this property, please check the data type About diff --git a/src/Umbraco.Web/Strategies/DataTypes/LegacyUploadFieldWorkaround.cs b/src/Umbraco.Web/Strategies/DataTypes/LegacyUploadFieldWorkaround.cs index 65aebcce0b..1de385caff 100644 --- a/src/Umbraco.Web/Strategies/DataTypes/LegacyUploadFieldWorkaround.cs +++ b/src/Umbraco.Web/Strategies/DataTypes/LegacyUploadFieldWorkaround.cs @@ -29,7 +29,7 @@ namespace Umbraco.Web.Strategies.DataTypes { if (UmbracoSettings.ImageAutoFillImageProperties != null) { - var property = sender.GenericProperties.FirstOrDefault(x => x.PropertyType.DataTypeDefinition.DataType.Id == new Guid(Constants.PropertyEditors.UploadField)); + var property = sender.GenericProperties.FirstOrDefault(x =>x.PropertyType.DataTypeDefinition.DataType!=null && x.PropertyType.DataTypeDefinition.DataType.Id == new Guid(Constants.PropertyEditors.UploadField)); if (property == null) return; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentControl.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentControl.cs index 589682a18c..4431a89e7e 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentControl.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentControl.cs @@ -436,196 +436,224 @@ namespace umbraco.controls private void AddControlNew(Property p, TabPage tp, string cap) { IDataType dt = p.PropertyType.DataTypeDefinition.DataType; - dt.DataEditor.Editor.ID = string.Format("prop_{0}", p.PropertyType.Alias); - dt.Data.PropertyId = p.Id; - //Add the DataType to an internal dictionary, which will be used to call the save method on the IDataEditor - //and to retrieve the value from IData in editContent.aspx.cs, so that it can be set on the legacy Document class. - DataTypes.Add(p.PropertyType.Alias, dt); - - // check for buttons - IDataFieldWithButtons df1 = dt.DataEditor.Editor as IDataFieldWithButtons; - if (df1 != null) + //check that property editor has been set for the data type used by this property + if (dt != null) { - ((Control)df1).ID = p.PropertyType.Alias; + dt.DataEditor.Editor.ID = string.Format("prop_{0}", p.PropertyType.Alias); + + dt.Data.PropertyId = p.Id; + + //Add the DataType to an internal dictionary, which will be used to call the save method on the IDataEditor + //and to retrieve the value from IData in editContent.aspx.cs, so that it can be set on the legacy Document class. + DataTypes.Add(p.PropertyType.Alias, dt); + + // check for buttons + IDataFieldWithButtons df1 = dt.DataEditor.Editor as IDataFieldWithButtons; + if (df1 != null) + { + ((Control)df1).ID = p.PropertyType.Alias; - if (df1.MenuIcons.Length > 0) + if (df1.MenuIcons.Length > 0) + tp.Menu.InsertSplitter(); + + + // Add buttons + int c = 0; + bool atEditHtml = false; + bool atSplitter = false; + foreach (object o in df1.MenuIcons) + { + try + { + MenuIconI m = (MenuIconI)o; + MenuIconI mi = tp.Menu.NewIcon(); + mi.ImageURL = m.ImageURL; + mi.OnClickCommand = m.OnClickCommand; + mi.AltText = m.AltText; + mi.ID = tp.ID + "_" + m.ID; + + if (m.ID == "html") + atEditHtml = true; + else + atEditHtml = false; + + atSplitter = false; + } + catch + { + tp.Menu.InsertSplitter(); + atSplitter = true; + } + + // Testing custom styles in editor + if (atSplitter && atEditHtml && dt.DataEditor.TreatAsRichTextEditor) + { + DropDownList ddl = tp.Menu.NewDropDownList(); + + ddl.Style.Add("margin-bottom", "5px"); + ddl.Items.Add(ui.Text("buttons", "styleChoose", null)); + ddl.ID = tp.ID + "_editorStyle"; + if (StyleSheet.GetAll().Length > 0) + { + foreach (StyleSheet s in StyleSheet.GetAll()) + { + foreach (StylesheetProperty sp in s.Properties) + { + ddl.Items.Add(new ListItem(sp.Text, sp.Alias)); + } + } + } + ddl.Attributes.Add("onChange", "addStyle(this, '" + p.PropertyType.Alias + "');"); + atEditHtml = false; + } + c++; + } + } + + // check for element additions + IMenuElement menuElement = dt.DataEditor.Editor as IMenuElement; + if (menuElement != null) + { + // add separator tp.Menu.InsertSplitter(); + // add the element + tp.Menu.NewElement(menuElement.ElementName, menuElement.ElementIdPreFix + p.Id.ToString(), + menuElement.ElementClass, menuElement.ExtraMenuWidth); + } - // Add buttons - int c = 0; - bool atEditHtml = false; - bool atSplitter = false; - foreach (object o in df1.MenuIcons) + Pane pp = new Pane(); + Control holder = new Control(); + holder.Controls.Add(dt.DataEditor.Editor); + if (p.PropertyType.DataTypeDefinition.DataType.DataEditor.ShowLabel) + { + string caption = p.PropertyType.Name; + if (p.PropertyType.Description != null && p.PropertyType.Description != String.Empty) + switch (UmbracoSettings.PropertyContextHelpOption) + { + case "icon": + caption += " \"""; + break; + case "text": + caption += "
    " + umbraco.library.ReplaceLineBreaks(p.PropertyType.Description) + ""; + break; + } + pp.addProperty(caption, holder); + } + else + pp.addProperty(holder); + + // Validation + if (p.PropertyType.Mandatory) { try { - MenuIconI m = (MenuIconI)o; - MenuIconI mi = tp.Menu.NewIcon(); - mi.ImageURL = m.ImageURL; - mi.OnClickCommand = m.OnClickCommand; - mi.AltText = m.AltText; - mi.ID = tp.ID + "_" + m.ID; - - if (m.ID == "html") - atEditHtml = true; - else - atEditHtml = false; - - atSplitter = false; - } - catch - { - tp.Menu.InsertSplitter(); - atSplitter = true; - } - - // Testing custom styles in editor - if (atSplitter && atEditHtml && dt.DataEditor.TreatAsRichTextEditor) - { - DropDownList ddl = tp.Menu.NewDropDownList(); - - ddl.Style.Add("margin-bottom", "5px"); - ddl.Items.Add(ui.Text("buttons", "styleChoose", null)); - ddl.ID = tp.ID + "_editorStyle"; - if (StyleSheet.GetAll().Length > 0) + var rq = new RequiredFieldValidator { - foreach (StyleSheet s in StyleSheet.GetAll()) - { - foreach (StylesheetProperty sp in s.Properties) - { - ddl.Items.Add(new ListItem(sp.Text, sp.Alias)); - } - } + ControlToValidate = dt.DataEditor.Editor.ID, + CssClass = "error" + }; + rq.Style.Add(HtmlTextWriterStyle.Display, "block"); + rq.Style.Add(HtmlTextWriterStyle.Padding, "2px"); + var component = dt.DataEditor.Editor; // holder.FindControl(rq.ControlToValidate); + var attribute = (ValidationPropertyAttribute)TypeDescriptor.GetAttributes(component)[typeof(ValidationPropertyAttribute)]; + PropertyDescriptor pd = null; + if (attribute != null) + { + pd = TypeDescriptor.GetProperties(component, null)[attribute.Name]; + } + if (pd != null) + { + rq.EnableClientScript = false; + rq.Display = ValidatorDisplay.Dynamic; + string[] errorVars = { p.PropertyType.Name, cap }; + rq.ErrorMessage = ui.Text("errorHandling", "errorMandatory", errorVars, null) + "
    "; + holder.Controls.AddAt(0, rq); } - ddl.Attributes.Add("onChange", "addStyle(this, '" + p.PropertyType.Alias + "');"); - atEditHtml = false; } - c++; - } - } - - // check for element additions - IMenuElement menuElement = dt.DataEditor.Editor as IMenuElement; - if (menuElement != null) - { - // add separator - tp.Menu.InsertSplitter(); - - // add the element - tp.Menu.NewElement(menuElement.ElementName, menuElement.ElementIdPreFix + p.Id.ToString(), - menuElement.ElementClass, menuElement.ExtraMenuWidth); - } - - Pane pp = new Pane(); - Control holder = new Control(); - holder.Controls.Add(dt.DataEditor.Editor); - if (p.PropertyType.DataTypeDefinition.DataType.DataEditor.ShowLabel) - { - string caption = p.PropertyType.Name; - if (p.PropertyType.Description != null && p.PropertyType.Description != String.Empty) - switch (UmbracoSettings.PropertyContextHelpOption) + catch (Exception valE) { - case "icon": - caption += " \"""; - break; - case "text": - caption += "
    " + umbraco.library.ReplaceLineBreaks(p.PropertyType.Description) + ""; - break; + HttpContext.Current.Trace.Warn("contentControl", + "EditorControl (" + dt.DataTypeName + ") does not support validation", + valE); } - pp.addProperty(caption, holder); - } - else - pp.addProperty(holder); + } - // Validation - if (p.PropertyType.Mandatory) - { - try + // RegExp Validation + if (p.PropertyType.ValidationRegExp != "") { - var rq = new RequiredFieldValidator + try + { + var rv = new RegularExpressionValidator { ControlToValidate = dt.DataEditor.Editor.ID, CssClass = "error" }; - rq.Style.Add(HtmlTextWriterStyle.Display, "block"); - rq.Style.Add(HtmlTextWriterStyle.Padding, "2px"); - var component = dt.DataEditor.Editor; // holder.FindControl(rq.ControlToValidate); - var attribute = (ValidationPropertyAttribute)TypeDescriptor.GetAttributes(component)[typeof(ValidationPropertyAttribute)]; - PropertyDescriptor pd = null; - if (attribute != null) - { - pd = TypeDescriptor.GetProperties(component, null)[attribute.Name]; - } - if (pd != null) - { - rq.EnableClientScript = false; - rq.Display = ValidatorDisplay.Dynamic; - string[] errorVars = { p.PropertyType.Name, cap }; - rq.ErrorMessage = ui.Text("errorHandling", "errorMandatory", errorVars, null) + "
    "; - holder.Controls.AddAt(0, rq); - } - } - catch (Exception valE) - { - HttpContext.Current.Trace.Warn("contentControl", - "EditorControl (" + dt.DataTypeName + ") does not support validation", - valE); - } - } - - // RegExp Validation - if (p.PropertyType.ValidationRegExp != "") - { - try - { - var rv = new RegularExpressionValidator + rv.Style.Add(HtmlTextWriterStyle.Display, "block"); + rv.Style.Add(HtmlTextWriterStyle.Padding, "2px"); + var component = dt.DataEditor.Editor; // holder.FindControl(rq.ControlToValidate); + var attribute = (ValidationPropertyAttribute)TypeDescriptor.GetAttributes(component)[typeof(ValidationPropertyAttribute)]; + PropertyDescriptor pd = null; + if (attribute != null) { - ControlToValidate = dt.DataEditor.Editor.ID, - CssClass = "error" - }; - rv.Style.Add(HtmlTextWriterStyle.Display, "block"); - rv.Style.Add(HtmlTextWriterStyle.Padding, "2px"); - var component = dt.DataEditor.Editor; // holder.FindControl(rq.ControlToValidate); - var attribute = (ValidationPropertyAttribute)TypeDescriptor.GetAttributes(component)[typeof(ValidationPropertyAttribute)]; - PropertyDescriptor pd = null; - if (attribute != null) - { - pd = TypeDescriptor.GetProperties(component, null)[attribute.Name]; + pd = TypeDescriptor.GetProperties(component, null)[attribute.Name]; + } + if (pd != null) + { + rv.ValidationExpression = p.PropertyType.ValidationRegExp; + rv.EnableClientScript = false; + rv.Display = ValidatorDisplay.Dynamic; + string[] errorVars = { p.PropertyType.Name, cap }; + rv.ErrorMessage = ui.Text("errorHandling", "errorRegExp", errorVars, null) + "
    "; + holder.Controls.AddAt(0, rv); + } } - if (pd != null) + catch (Exception valE) { - rv.ValidationExpression = p.PropertyType.ValidationRegExp; - rv.EnableClientScript = false; - rv.Display = ValidatorDisplay.Dynamic; - string[] errorVars = { p.PropertyType.Name, cap }; - rv.ErrorMessage = ui.Text("errorHandling", "errorRegExp", errorVars, null) + "
    "; - holder.Controls.AddAt(0, rv); + HttpContext.Current.Trace.Warn("contentControl", + "EditorControl (" + dt.DataTypeName + ") does not support validation", + valE); } } - catch (Exception valE) - { - HttpContext.Current.Trace.Warn("contentControl", - "EditorControl (" + dt.DataTypeName + ") does not support validation", - valE); - } - } - // This is once again a nasty nasty hack to fix gui when rendering wysiwygeditor - if (dt.DataEditor.TreatAsRichTextEditor) - { - tp.Controls.Add(dt.DataEditor.Editor); + // This is once again a nasty nasty hack to fix gui when rendering wysiwygeditor + if (dt.DataEditor.TreatAsRichTextEditor) + { + tp.Controls.Add(dt.DataEditor.Editor); + } + else + { + Panel ph = new Panel(); + ph.Attributes.Add("style", "padding: 0; position: relative;"); // NH 4.7.1, latest styles added to support CP item: 30363 + ph.Controls.Add(pp); + + tp.Controls.Add(ph); + } } else { - Panel ph = new Panel(); - ph.Attributes.Add("style", "padding: 0; position: relative;"); // NH 4.7.1, latest styles added to support CP item: 30363 + + var ph = new Panel(); + + var pp = new Pane(); + + var missingPropertyEditorLabel = new Literal + { + Text = ui.Text("errors", "missingPropertyEditorErrorMessage") + }; + + pp.addProperty(p.PropertyType.Name, missingPropertyEditorLabel); + + ph.Attributes.Add("style", "padding: 0; position: relative;"); + ph.Controls.Add(pp); tp.Controls.Add(ph); } + + } public enum publishModes diff --git a/src/umbraco.cms/businesslogic/Property/Property.cs b/src/umbraco.cms/businesslogic/Property/Property.cs index d34f234d5a..47e16b8769 100644 --- a/src/umbraco.cms/businesslogic/Property/Property.cs +++ b/src/umbraco.cms/businesslogic/Property/Property.cs @@ -1,5 +1,7 @@ using System; +using System.Reflection.Emit; using System.Runtime.CompilerServices; +using System.Web.UI; using System.Xml; using Umbraco.Core; using Umbraco.Core.Persistence; @@ -64,9 +66,19 @@ namespace umbraco.cms.businesslogic.property //Just to ensure that there is a PropertyType available _pt = PropertyType.GetPropertyType(property.PropertyTypeId); - if (_pt.DataTypeDefinition.DataType == null) - throw new Exception(string.Format("Could not load datatype '{0}'", _pt.DataTypeDefinition.Text)); - _data = _pt.DataTypeDefinition.DataType.Data; + + //ensure we have data property editor set + if (_pt.DataTypeDefinition.DataType != null) + { + _data = _pt.DataTypeDefinition.DataType.Data; + } + else + { + //send back null we will handle it in ContentControl AddControlNew + //and display to use message from the dictionary errors section + _data= new DefaultData(null); + } + _data.PropertyId = Id; //set the value so it doesn't need to go to the database diff --git a/src/umbraco.cms/businesslogic/datatype/DefaultData.cs b/src/umbraco.cms/businesslogic/datatype/DefaultData.cs index 16be25827c..feb7783187 100644 --- a/src/umbraco.cms/businesslogic/datatype/DefaultData.cs +++ b/src/umbraco.cms/businesslogic/datatype/DefaultData.cs @@ -81,7 +81,10 @@ namespace umbraco.cms.businesslogic.datatype //instead of making it query for itself. This is a peformance optimization enhancement. var dbType = BaseDataType.GetDBType(strDbType); var fieldName = BaseDataType.GetDataFieldName(dbType); - _dataType.SetDataTypeProperties(fieldName, dbType); + + //if misconfigured (datatype created in the tree, but save button never clicked), the datatype will be null + if(_dataType != null) + _dataType.SetDataTypeProperties(fieldName, dbType); //ensures that it doesn't go back to the db _valueLoaded = true; @@ -102,7 +105,7 @@ namespace umbraco.cms.businesslogic.datatype .Where(x => x.Id == _propertyId); var dto = Database.Fetch(sql).FirstOrDefault(); - if (dto != null) + if (dto != null && _dataType != null) { //the type stored in the cmsDataType table var strDbType = dto.PropertyTypeDto.DataTypeDto.DbType; From a49058fe00695c8c50e62c8821b989e0fc55fb66 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sun, 29 Dec 2013 15:07:28 +0100 Subject: [PATCH 25/27] Fixes U4-510 Copy node makes multiple copies --- src/Umbraco.Web.UI/umbraco/dialogs/moveOrCopy.aspx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/moveOrCopy.aspx b/src/Umbraco.Web.UI/umbraco/dialogs/moveOrCopy.aspx index 43f17f096b..7323eb0ca1 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/moveOrCopy.aspx +++ b/src/Umbraco.Web.UI/umbraco/dialogs/moveOrCopy.aspx @@ -83,7 +83,7 @@

    - +   <%=umbraco.ui.Text("general", "or", UmbracoUser)%>   <%=umbraco.ui.Text("general", "cancel", UmbracoUser)%> From fc0c6a1cd80cb22c661aa5c46754be18ef8f9d9d Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 31 Dec 2013 13:27:47 +1100 Subject: [PATCH 26/27] fixes stored password check with legacy encoding. --- src/Umbraco.Core/Security/MembershipProviderBase.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs index 5480b8220c..901bec808b 100644 --- a/src/Umbraco.Core/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs @@ -748,6 +748,12 @@ namespace Umbraco.Core.Security /// internal string StoredPassword(string storedString, out string salt) { + if (UseLegacyEncoding) + { + salt = string.Empty; + return storedString; + } + switch (PasswordFormat) { case MembershipPasswordFormat.Hashed: From 17883358b6869bbf58e99cbb7fdbd2108cda214d Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 2 Jan 2014 14:13:43 +1100 Subject: [PATCH 27/27] Fixes some issues with the User membership provider, updates user create dialog to also have an email address (which is required by membership), adds validation to the member and user create dialogs to ensure that the email address is validated to be a correcty formatted address. --- .../Security/MembershipProviderBase.cs | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 8 + .../umbraco/config/create/UI.xml | 2 +- src/Umbraco.Web.UI/umbraco/create/User.ascx | 35 +++++ .../umbraco/create/User.ascx.cs | 84 +++++++++++ .../umbraco/create/User.ascx.designer.cs | 105 +++++++++++++ src/Umbraco.Web.UI/umbraco/create/member.ascx | 11 +- .../Application/UmbracoApplicationActions.js | 142 ++++++++++-------- .../umbraco/create/member.ascx.cs | 27 +--- .../umbraco/create/member.ascx.designer.cs | 11 +- .../umbraco/create/userTasks.cs | 19 ++- .../UsersMembershipProvider.cs | 16 +- 12 files changed, 358 insertions(+), 104 deletions(-) create mode 100644 src/Umbraco.Web.UI/umbraco/create/User.ascx create mode 100644 src/Umbraco.Web.UI/umbraco/create/User.ascx.cs create mode 100644 src/Umbraco.Web.UI/umbraco/create/User.ascx.designer.cs diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs index 901bec808b..71fb729b78 100644 --- a/src/Umbraco.Core/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs @@ -605,7 +605,7 @@ namespace Umbraco.Core.Security return pass; } - protected bool IsEmailValid(string email) + internal static bool IsEmailValid(string email) { const string pattern = @"^(?!\.)(""([^""\r\\]|\\[""\r\\])*""|" + @"([-a-z0-9!#$%&'*+/=?^_`{|}~]|(? PartialViewMacro.ascx + + User.ascx + ASPXCodeBehind + + + User.ascx + ExamineManagement.ascx ASPXCodeBehind @@ -589,6 +596,7 @@ + diff --git a/src/Umbraco.Web.UI/umbraco/config/create/UI.xml b/src/Umbraco.Web.UI/umbraco/config/create/UI.xml index 88105d0ad2..3f063c134a 100644 --- a/src/Umbraco.Web.UI/umbraco/config/create/UI.xml +++ b/src/Umbraco.Web.UI/umbraco/config/create/UI.xml @@ -70,7 +70,7 @@

    User
    - /create/simple.ascx + /create/user.ascx diff --git a/src/Umbraco.Web.UI/umbraco/create/User.ascx b/src/Umbraco.Web.UI/umbraco/create/User.ascx new file mode 100644 index 0000000000..deb139c557 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco/create/User.ascx @@ -0,0 +1,35 @@ +<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="User.ascx.cs" Inherits="Umbraco.Web.UI.Umbraco.Create.User" %> +<%@ Import Namespace="umbraco" %> + +

    + Login Name: + * + +
    + +

    +

    + E-mail: + * + + +
    + +

    + +