You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by pg...@apache.org on 2018/09/27 16:01:36 UTC

svn commit: r1842110 - in /ofbiz/ofbiz-framework/trunk: applications/party/template/party/profileblocks/ applications/party/widget/partymgr/ docs/asciidoc/ framework/common/config/ framework/common/data/ framework/common/servicedef/ framework/common/sr...

Author: pgil
Date: Thu Sep 27 16:01:35 2018
New Revision: 1842110

URL: http://svn.apache.org/viewvc?rev=1842110&view=rev
Log:
Implemented : Impersonation of userLogin feature
(OFBIZ-10515)

Introduce a new feature that allow the impersonation of a login by an authorized user.
Add the documentation with all the details in security-impersonation.adoc.
Big thanks to Leila, Nicolas and Jacques for your contribution to this implementation.
Big thanks to Mathieu, Jacques, Pierre and Taher for your remarks and reviews that
improved this contribution quality. 

Added:
    ofbiz/ofbiz-framework/trunk/framework/security/src/doc/
    ofbiz/ofbiz-framework/trunk/framework/security/src/doc/asciidoc/
    ofbiz/ofbiz-framework/trunk/framework/security/src/doc/asciidoc/_include/
    ofbiz/ofbiz-framework/trunk/framework/security/src/doc/asciidoc/_include/security-impersonation.adoc   (with props)
    ofbiz/ofbiz-framework/trunk/framework/security/src/doc/asciidoc/security.adoc   (with props)
    ofbiz/ofbiz-framework/trunk/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java   (with props)
    ofbiz/ofbiz-framework/trunk/themes/common-theme/template/ImpersonateBanner.ftl   (with props)
    ofbiz/ofbiz-framework/trunk/themes/common-theme/template/Impersonated.ftl   (with props)
    ofbiz/ofbiz-framework/trunk/themes/common-theme/webapp/common/css/
    ofbiz/ofbiz-framework/trunk/themes/common-theme/webapp/common/css/impersonate.css   (with props)
    ofbiz/ofbiz-framework/trunk/themes/common-theme/webapp/images/img/impersonate-ico.png   (with props)
Modified:
    ofbiz/ofbiz-framework/trunk/applications/party/template/party/profileblocks/UserLogin.ftl
    ofbiz/ofbiz-framework/trunk/applications/party/template/party/profileblocks/Visits.ftl
    ofbiz/ofbiz-framework/trunk/applications/party/widget/partymgr/PartyMenus.xml
    ofbiz/ofbiz-framework/trunk/applications/party/widget/partymgr/PartyVisitForms.xml
    ofbiz/ofbiz-framework/trunk/docs/asciidoc/developer-manual.adoc
    ofbiz/ofbiz-framework/trunk/framework/common/config/CommonUiLabels.xml
    ofbiz/ofbiz-framework/trunk/framework/common/config/SecurityextUiLabels.xml
    ofbiz/ofbiz-framework/trunk/framework/common/data/CommonSecurityGroupDemoData.xml
    ofbiz/ofbiz-framework/trunk/framework/common/data/CommonSecurityPermissionSeedData.xml
    ofbiz/ofbiz-framework/trunk/framework/common/data/CommonTypeData.xml
    ofbiz/ofbiz-framework/trunk/framework/common/servicedef/services.xml
    ofbiz/ofbiz-framework/trunk/framework/common/src/main/java/org/apache/ofbiz/common/login/LoginServices.java
    ofbiz/ofbiz-framework/trunk/framework/common/webcommon/WEB-INF/common-controller.xml
    ofbiz/ofbiz-framework/trunk/framework/common/widget/CommonScreens.xml
    ofbiz/ofbiz-framework/trunk/framework/security/config/security.properties
    ofbiz/ofbiz-framework/trunk/framework/security/data/SecurityGroupDemoData.xml
    ofbiz/ofbiz-framework/trunk/framework/security/entitydef/entitymodel.xml
    ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginWorker.java
    ofbiz/ofbiz-framework/trunk/themes/bluelight/template/Header.ftl
    ofbiz/ofbiz-framework/trunk/themes/common-theme/widget/CommonScreens.xml
    ofbiz/ofbiz-framework/trunk/themes/common-theme/widget/Theme.xml
    ofbiz/ofbiz-framework/trunk/themes/flatgrey/template/Header.ftl
    ofbiz/ofbiz-framework/trunk/themes/rainbowstone/template/includes/TopAppBar.ftl
    ofbiz/ofbiz-framework/trunk/themes/tomahawk/template/Header.ftl

Modified: ofbiz/ofbiz-framework/trunk/applications/party/template/party/profileblocks/UserLogin.ftl
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/party/template/party/profileblocks/UserLogin.ftl?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/party/template/party/profileblocks/UserLogin.ftl (original)
+++ ofbiz/ofbiz-framework/trunk/applications/party/template/party/profileblocks/UserLogin.ftl Thu Sep 27 16:01:35 2018
@@ -53,6 +53,9 @@ under the License.
                 <#if security.hasEntityPermission("SECURITY", "_VIEW", session)>
                   <a href="<@o...@ofbizUrl>">${uiLabelMap.SecurityGroups}</a>
                 </#if>
+                <#if security.hasEntityPermission("IMPERSONATE", "_ADMIN", session)>
+                    <a href="<@o...@ofbizUrl>">${uiLabelMap.CommonImpersonate}</a>
+                </#if>
               </td>
             </tr>
           </#list>

Modified: ofbiz/ofbiz-framework/trunk/applications/party/template/party/profileblocks/Visits.ftl
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/party/template/party/profileblocks/Visits.ftl?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/party/template/party/profileblocks/Visits.ftl (original)
+++ ofbiz/ofbiz-framework/trunk/applications/party/template/party/profileblocks/Visits.ftl Thu Sep 27 16:01:35 2018
@@ -31,6 +31,7 @@ under the License.
           <tr class="header-row">
             <td>${uiLabelMap.PartyVisitId}</td>
             <td>${uiLabelMap.PartyUserLogin}</td>
+            <td>${uiLabelMap.CommonImpersonateUserLogin}</td>
             <td>${uiLabelMap.PartyNewUser}</td>
             <td>${uiLabelMap.PartyWebApp}</td>
             <td>${uiLabelMap.PartyClientIP}</td>
@@ -39,11 +40,16 @@ under the License.
           </tr>
           <#list visits as visitObj>
             <#if (visitObj_index > 4)><#break></#if>
+            <#assign userLoginHistory = EntityQuery.use(delegator).from("UserLoginHistory").where('visitId',visitObj.visitId!).queryFirst()!>
+            <#if userLoginHistory??>
+                <#assign impersonateUserLoginId = userLoginHistory.originUserLoginId!>
+            </#if>
               <tr>
                 <td class="button-col">
                   <a href="<@o...@ofbizUrl>">${visitObj.visitId!}</a>
                 </td>
                 <td>${visitObj.userLoginId!}</td>
+                <td>${impersonateUserLoginId!}</td>
                 <td>${visitObj.userCreated!}</td>
                 <td>${visitObj.webappName!}</td>
                 <td>${visitObj.clientIpAddress!}</td>

Modified: ofbiz/ofbiz-framework/trunk/applications/party/widget/partymgr/PartyMenus.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/party/widget/partymgr/PartyMenus.xml?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/party/widget/partymgr/PartyMenus.xml (original)
+++ ofbiz/ofbiz-framework/trunk/applications/party/widget/partymgr/PartyMenus.xml Thu Sep 27 16:01:35 2018
@@ -772,5 +772,11 @@
             </link>
         </menu-item>
     </menu>
-    
+    <menu name="listAllVisits">
+        <menu-item name="listAll" title="${uiLabelMap.CommonListAll}">
+            <link target="findVisits">
+                <parameter param-name="partyId" from-field="parameters.partyId"/>
+            </link>
+        </menu-item>
+    </menu>
 </menus>

Modified: ofbiz/ofbiz-framework/trunk/applications/party/widget/partymgr/PartyVisitForms.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/party/widget/partymgr/PartyVisitForms.xml?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/party/widget/partymgr/PartyVisitForms.xml (original)
+++ ofbiz/ofbiz-framework/trunk/applications/party/widget/partymgr/PartyVisitForms.xml Thu Sep 27 16:01:35 2018
@@ -41,7 +41,6 @@ under the License.
         odd-row-style="alternate-row" header-row-style="header-row-2" default-table-style="basic-table hover-bar">
         <actions>
             <set field="parameters.sortField" from-field="parameters.sortField" default-value="-visitId"/>
-
             <service service-name="performFind" result-map="result" result-map-list="listIt">
                 <field-map field-name="inputFields" from-field="parameters"/>
                 <field-map field-name="entityName" value="Visit"/>
@@ -51,7 +50,15 @@ under the License.
                 <field-map field-name="filterByDate" from-field="parameters.activeOnly"/>
             </service>
         </actions>
-        
+        <row-actions>
+            <entity-condition entity-name="UserLoginHistory" list="userLoginHistoryList">
+                <condition-list>
+                    <condition-expr field-name="visitId" from-field="visitId"/>
+                </condition-list>
+                <select-field field-name="userLoginId"/>
+            </entity-condition>
+            <set field="impersonateUserLoginId" from-field="userLoginHistoryList[0].originUserLoginId" type="String"/>
+        </row-actions>
         <field name="visitId" widget-style="buttontext" sort-field="true">
             <hyperlink description="${visitId}" target="visitdetail">
                 <parameter param-name="visitId"/>
@@ -64,6 +71,7 @@ under the License.
             </hyperlink>
         </field>
         <field name="userLoginId" title="${uiLabelMap.CommonUserLoginId}" sort-field="true"><display/></field>
+        <field name="impersonateUserLoginId" title="${uiLabelMap.CommonImpersonateUserLogin}"><display/></field>
         <field name="userCreated" title="${uiLabelMap.PartyNewUser}" sort-field="true"><display/></field>
         <field name="webappName" title="${uiLabelMap.PartyWebApp}" sort-field="true"><display/></field>
         <field name="clientIpAddress" title="${uiLabelMap.PartyClientIP}" sort-field="true"><display/></field>

Modified: ofbiz/ofbiz-framework/trunk/docs/asciidoc/developer-manual.adoc
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/docs/asciidoc/developer-manual.adoc?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/docs/asciidoc/developer-manual.adoc (original)
+++ ofbiz/ofbiz-framework/trunk/docs/asciidoc/developer-manual.adoc Thu Sep 27 16:01:35 2018
@@ -269,6 +269,6 @@ displays search results.
 
 == Deployment
 
-== Security
+include::../../framework/security/src/doc/asciidoc/security.adoc[leveloffset=+1]
 
 == Appendices

Modified: ofbiz/ofbiz-framework/trunk/framework/common/config/CommonUiLabels.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/common/config/CommonUiLabels.xml?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/common/config/CommonUiLabels.xml (original)
+++ ofbiz/ofbiz-framework/trunk/framework/common/config/CommonUiLabels.xml Thu Sep 27 16:01:35 2018
@@ -5517,6 +5517,22 @@
         <value xml:lang="zh">图片</value>
         <value xml:lang="zh-TW">圖片</value>
     </property>
+    <property key="CommonImpersonate">
+        <value xml:lang="en">Impersonate</value>
+        <value xml:lang="fr">Incarner</value>
+    </property>
+    <property key="CommonImpersonateUserLogin">
+        <value xml:lang="en">User impersonated</value>
+        <value xml:lang="fr">Utilisateur incarné</value>
+    </property>
+    <property key="CommonImpersonateTitle">
+        <value xml:lang="en">Impersonation in process</value>
+        <value xml:lang="fr">Incarnation en cours</value>
+    </property>
+    <property key="CommonImpersonateStop">
+        <value xml:lang="en">Stop impersonation</value>
+        <value xml:lang="fr">Arrêter l'incarnation</value>
+    </property>
     <property key="CommonImport">
         <value xml:lang="en">Import</value>
         <value xml:lang="zh">导入</value>
@@ -8231,6 +8247,13 @@
         <value xml:lang="zh">机构图标</value>
         <value xml:lang="zh-TW">機構商標</value>
     </property>
+    <property key="CommonOriginUserLoginId">
+        <value xml:lang="de">Herkunft Benutzeranmeldung ID</value>
+        <value xml:lang="en">Origin User Login ID</value>
+        <value xml:lang="es">Código de usuario de origen</value>
+        <value xml:lang="fr">Identifiant de connexion d'origine</value>
+        <value xml:lang="it">Codice utente di origine</value>
+    </property>
     <property key="CommonOther">
         <value xml:lang="ar">أخرى</value>
         <value xml:lang="cs">Jiný(á)</value>
@@ -14885,6 +14908,10 @@
         <value xml:lang="zh-CN">编辑数据源类型</value>
         <value xml:lang="zh-TW">編輯資料源類型</value>
     </property>
+    <property key="PageTitleImpersonated">
+        <value xml:lang="en">Impersonated</value>
+        <value xml:lang="fr">Substitué</value>
+    </property>
     <property key="PageTitleListDataSource">
         <value xml:lang="ar">قائمة مصادر المعلومات</value>
         <value xml:lang="cs">Seznam datových zdrojů</value>

Modified: ofbiz/ofbiz-framework/trunk/framework/common/config/SecurityextUiLabels.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/common/config/SecurityextUiLabels.xml?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/common/config/SecurityextUiLabels.xml (original)
+++ ofbiz/ofbiz-framework/trunk/framework/common/config/SecurityextUiLabels.xml Thu Sep 27 16:01:35 2018
@@ -107,6 +107,26 @@
         <value xml:lang="zh">登录时发生下列错误:${errorMessage}</value>
         <value xml:lang="zh-TW">登入時發生錯誤: ${errorMessage}.</value>
     </property>
+    <property key="loginevents.impersonate_yourself">
+        <value xml:lang="en">You can't impersonate yourself ... What are you trying to do ?</value>
+        <value xml:lang="fr">Vous ne pouvez pas vous incarner vous même ... Qu'essayez-vous donc de faire ?</value>
+    </property>
+    <property key="loginevents.impersonate_notEnoughPermission">
+        <value xml:lang="en">You cannot impersonate a login with more permission than you have : ${missingPermissions}</value>
+        <value xml:lang="fr">Vous ne pouvez pas incarner un utilisateur avec un niveau de permission supérieur au vôtre : ${missingPermissions}</value>
+    </property>
+    <property key="loginevents.impersonate_notAdmin">
+        <value xml:lang="en">You cannot impersonate a login with ADMIN permission</value>
+        <value xml:lang="fr">Vous ne pouvez pas incarner un utilisateur avec un niveau de permission ADMIN</value>
+    </property>
+    <property key="loginevents.impersonation_disabled">
+        <value xml:lang="en">Impersonation feature is disabled, please check security configuration</value>
+        <value xml:lang="fr">La fonctionnalité d'incarnation est désactivée, merci de vérifier la configuration sécurité</value>
+    </property>
+    <property key="loginevents.impersonation_in_process">
+        <value xml:lang="en">Impersonation of your user is in process by ${originUserLoginId}</value>
+        <value xml:lang="fr">Incarnation de votre utilisateur en cours par ${originUserLoginId}</value>
+    </property>
     <property key="loginevents.new_password_createdandsent_check_email">
         <value xml:lang="de">Ein neues Passwort wurde erzeugt und Ihnen per E-Mail zugeschickt. Überprüfen Sie bitte Ihren E-Mail-Eingang.</value>
         <value xml:lang="en">A new password has been created and sent to you. Please check your Email.</value>
@@ -245,6 +265,14 @@
         <value xml:lang="zh">配置出错;请与客户服务联系。</value>
         <value xml:lang="zh-TW">設定有問題: 請聯絡客服.</value>
     </property>
+    <property key="loginevents.origin_username_is_present">
+        <value xml:lang="en">You already have an impersonation in process. Please leave it before continue</value>
+        <value xml:lang="fr">Vous avez déjà une incarnation en cours, veuillez mettre fin à celle-ci avant de continuer</value>
+    </property>
+    <property key="loginevents.problem_getting_security_question_record">
+        <value xml:lang="en">Problem getting User Login Security Question record.</value>
+        <value xml:lang="fr">Problème durant la lecture de votre question de sécurité</value>
+    </property>
     <property key="loginevents.unable_to_login_tenant">
         <value xml:lang="en">You cannot login to this tenant</value>
         <value xml:lang="es">No puede conectarse con esta organización</value>
@@ -865,4 +893,4 @@
         <value xml:lang="zh">将重新启用 ${reEnableTime}。</value>
         <value xml:lang="zh-TW">將重新啟用 ${reEnableTime}.</value>
     </property>
-</resource>
\ No newline at end of file
+</resource>

Modified: ofbiz/ofbiz-framework/trunk/framework/common/data/CommonSecurityGroupDemoData.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/common/data/CommonSecurityGroupDemoData.xml?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/common/data/CommonSecurityGroupDemoData.xml (original)
+++ ofbiz/ofbiz-framework/trunk/framework/common/data/CommonSecurityGroupDemoData.xml Thu Sep 27 16:01:35 2018
@@ -40,4 +40,9 @@
 
     <!-- Temporal expression security -->
     <SecurityGroupPermission fromDate="2001-05-13 12:00:00.0" groupId="FULLADMIN" permissionId="TEMPEXPR_ADMIN"/>
+
+    <!-- Impersonation security -->
+    <SecurityGroupPermission fromDate="2001-05-13 12:00:00.0" groupId="FULLADMIN" permissionId="IMPERSONATE_ADMIN"/>
+    <SecurityGroupPermission fromDate="2001-05-13 12:00:00.0" groupId="IMPERSONATION" permissionId="IMPERSONATE_ADMIN"/>
+    <SecurityGroupPermission fromDate="2001-05-13 12:00:00.0" groupId="IMPERSONATION" permissionId="SECURITY_VIEW"/>
 </entity-engine-xml>

Modified: ofbiz/ofbiz-framework/trunk/framework/common/data/CommonSecurityPermissionSeedData.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/common/data/CommonSecurityPermissionSeedData.xml?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/common/data/CommonSecurityPermissionSeedData.xml (original)
+++ ofbiz/ofbiz-framework/trunk/framework/common/data/CommonSecurityPermissionSeedData.xml Thu Sep 27 16:01:35 2018
@@ -36,12 +36,15 @@
 
     <!-- Temporal expression security -->
     <SecurityPermission description="Temporal expression admin" permissionId="TEMPEXPR_ADMIN"/>
-    
+    <!-- Impersonation security -->
+    <SecurityPermission description="Admin Impersonation operation" permissionId="IMPERSONATE_ADMIN"/>
+
     <!-- exception for SUPER user and group shoul be loaded as seed-->
     <SecurityGroupPermission fromDate="2001-05-13 12:00:00.0" groupId="SUPER" permissionId="COMMON_ADMIN"/>
     <SecurityGroupPermission fromDate="2001-05-13 12:00:00.0" groupId="SUPER" permissionId="PORTALPAGE_ADMIN"/>
     <SecurityGroupPermission fromDate="2001-05-13 12:00:00.0" groupId="SUPER" permissionId="VISUALTHEME_ADMIN"/>
     <SecurityGroupPermission fromDate="2001-05-13 12:00:00.0" groupId="SUPER" permissionId="USERPREF_ADMIN"/>
     <SecurityGroupPermission fromDate="2001-05-13 12:00:00.0" groupId="SUPER" permissionId="TEMPEXPR_ADMIN"/>
-    
+    <SecurityGroupPermission fromDate="2001-05-13 12:00:00.0" groupId="SUPER" permissionId="IMPERSONATE_ADMIN"/>
+
 </entity-engine-xml>

Modified: ofbiz/ofbiz-framework/trunk/framework/common/data/CommonTypeData.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/common/data/CommonTypeData.xml?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/common/data/CommonTypeData.xml (original)
+++ ofbiz/ofbiz-framework/trunk/framework/common/data/CommonTypeData.xml Thu Sep 27 16:01:35 2018
@@ -113,6 +113,7 @@ under the License.
     <Enumeration enumId="VT_CHPWD_TMPLT_LOC" description="Change Password Template Location" enumTypeId="VT_RES_TYPE" sequenceId="22"/>
     <Enumeration enumId="VT_FGPWD_TMPLT_LOC" description="Forget Password Template Location" enumTypeId="VT_RES_TYPE" sequenceId="23"/>
     <Enumeration enumId="VT_GSQUE_TMPLT_LOC" description="Security Question Template Location" enumTypeId="VT_RES_TYPE" sequenceId="24"/>
+    <Enumeration enumId="VT_IMPERSO_TMPLT_LOC" description="Impersonated Template Location" enumTypeId="VT_RES_TYPE" sequenceId="25"/>
     <Enumeration enumId="VT_STYLESHEET_LESS" description="Style Sheet Less URL" enumTypeId="VT_RES_TYPE" sequenceId="25"/>
 
     <VisualThemeSet visualThemeSetId="BACKOFFICE" description="Themes to be used for backoffice applications"/>

Modified: ofbiz/ofbiz-framework/trunk/framework/common/servicedef/services.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/common/servicedef/services.xml?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/common/servicedef/services.xml (original)
+++ ofbiz/ofbiz-framework/trunk/framework/common/servicedef/services.xml Thu Sep 27 16:01:35 2018
@@ -381,6 +381,18 @@ under the License.
         <implements service="authenticationInterface"/>
         <attribute name="request" mode="IN" type="javax.servlet.http.HttpServletRequest" optional="true"/>
     </service>
+    <service name="userImpersonate" engine="java" location="org.apache.ofbiz.common.login.LoginServices" invoke="userImpersonate" auth="true">
+        <description>Used to Automatically Authenticate a username/password; create a UserLogin object</description>
+        <required-permissions join-type="AND">
+            <check-permission permission="IMPERSONATE" action="ADMIN"/>
+        </required-permissions>
+        <implements service="authenticationInterface"/>
+        <attribute name="userLoginIdToImpersonate" type="String" mode="IN"/>
+        <attribute name="visitId" type="String" mode="IN" optional="true"/>
+        <attribute name="userLogin" type="org.apache.ofbiz.entity.GenericValue" mode="OUT"/>
+        <attribute name="userLoginSession" type="java.util.Map" mode="OUT" optional="true"/>
+        <attribute name="originUserLogin" type="org.apache.ofbiz.entity.GenericValue" mode="OUT"/>
+    </service>
     <service name="createUserLogin" engine="java" auth="false"
         location="org.apache.ofbiz.common.login.LoginServices" invoke="createUserLogin">
         <description>Create a UserLogin</description>

Modified: ofbiz/ofbiz-framework/trunk/framework/common/src/main/java/org/apache/ofbiz/common/login/LoginServices.java
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/common/src/main/java/org/apache/ofbiz/common/login/LoginServices.java?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/common/src/main/java/org/apache/ofbiz/common/login/LoginServices.java (original)
+++ ofbiz/ofbiz-framework/trunk/framework/common/src/main/java/org/apache/ofbiz/common/login/LoginServices.java Thu Sep 27 16:01:35 2018
@@ -20,6 +20,7 @@
 package org.apache.ofbiz.common.login;
 
 import java.sql.Timestamp;
+import java.util.Calendar;
 import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
@@ -27,6 +28,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
@@ -53,6 +55,7 @@ import org.apache.ofbiz.entity.util.Enti
 import org.apache.ofbiz.entity.util.EntityQuery;
 import org.apache.ofbiz.entity.util.EntityUtilProperties;
 import org.apache.ofbiz.security.Security;
+import org.apache.ofbiz.security.SecurityUtil;
 import org.apache.ofbiz.service.DispatchContext;
 import org.apache.ofbiz.service.GenericServiceException;
 import org.apache.ofbiz.service.LocalDispatcher;
@@ -435,6 +438,126 @@ public class LoginServices {
         return result;
     }
 
+    /**
+     * Login service to authenticate a username without password, storing history
+     *
+     * @return Map of results including (userLogin) GenericValue object
+     */
+    public static Map<String, Object> userImpersonate(DispatchContext ctx, Map<String, ?> context) {
+        Locale locale = (Locale) context.get("locale");
+        Delegator delegator = ctx.getDelegator();
+        Map<String, Object> result = ServiceUtil.returnSuccess();
+
+        String userLoginIdToImpersonate = (String) context.get("userLoginIdToImpersonate");
+        GenericValue originUserLogin = (GenericValue) context.get("userLogin");
+        // get the visitId for the history entity
+        String visitId = (String) context.get("visitId");
+
+        if ("true".equalsIgnoreCase(EntityUtilProperties.getPropertyValue("security", "username.lowercase", delegator))) {
+            userLoginIdToImpersonate = userLoginIdToImpersonate.toLowerCase();
+        }
+
+        GenericValue userLogin;
+        try {
+            userLogin = EntityQuery.use(delegator).from("UserLogin").where("userLoginId", userLoginIdToImpersonate).queryOne();
+        } catch (GenericEntityException e) {
+            Debug.logError(e, module);
+            return ServiceUtil.returnError(e.getMessage());
+        }
+
+        // check impersonation controls
+        String errorMessage = checkImpersonationControls(delegator, originUserLogin, userLogin, locale);
+        if (errorMessage != null) {
+            return ServiceUtil.returnError(errorMessage);
+        }
+
+        // return the UserLoginSession Map
+        Map<String, Object> userLoginSessionMap = LoginWorker.getUserLoginSession(userLogin);
+        if (userLoginSessionMap != null) {
+            result.put("userLoginSession", userLoginSessionMap);
+        }
+
+        // grab the hasLoggedOut flag
+        boolean hasLoggedOut = "Y".equalsIgnoreCase(userLogin.getString("hasLoggedOut"));
+        if (hasLoggedOut || UtilValidate.isEmpty(userLogin.getString("hasLoggedOut"))) {
+            userLogin.set("hasLoggedOut", "N");
+            try {
+                userLogin.store();
+            } catch (GenericEntityException e) {
+                Debug.logError(e, module);
+                return ServiceUtil.returnError(e.getMessage());
+            }
+        }
+
+        //Log impersonation in UserLoginHistory
+        Map<String, Object> historyCreateMap = UtilMisc.toMap("userLoginId", userLoginIdToImpersonate);
+        historyCreateMap.put("visitId", visitId);
+        historyCreateMap.put("fromDate", UtilDateTime.nowTimestamp());
+        historyCreateMap.put("successfulLogin", "Y");
+        historyCreateMap.put("partyId", userLogin.get("partyId"));
+        historyCreateMap.put("originUserLoginId", originUserLogin.get("userLoginId"));
+        //End impersonation in one hour max
+        historyCreateMap.put("thruDate", UtilDateTime.adjustTimestamp(UtilDateTime.nowTimestamp(), Calendar.HOUR, 1));
+        try {
+            delegator.create("UserLoginHistory", historyCreateMap);
+        } catch (GenericEntityException e) {
+            Debug.logError(e, module);
+            return ServiceUtil.returnError(e.getMessage());
+        }
+
+        result.put("userLogin", userLogin);
+        result.put("originUserLogin", originUserLogin);
+        return result;
+    }
+
+    /**
+     * Return error message if a needed control has failed :
+     * userLoginToImpersonate must exist
+     * Impersonation have to be enabled
+     * Check userLoginIdToImpersonate is active, not Admin and not equals to userLogin
+     * Check userLogin has enough permission
+     *
+     * @param delegator
+     * @param userLogin
+     * @param userLoginToImpersonate
+     * @param locale
+     * @return
+     */
+    private static String checkImpersonationControls(Delegator delegator, GenericValue userLogin, GenericValue userLoginToImpersonate, Locale locale) {
+        if (userLoginToImpersonate == null) {
+            return UtilProperties.getMessage(resource, "loginservices.username_missing", locale);
+        }
+        String userLoginId = userLogin.getString("userLoginId");
+        String userLoginIdToImpersonate = userLoginToImpersonate.getString("userLoginId");
+
+        if (UtilProperties.getPropertyAsBoolean("security", "security.disable.impersonation", true)) {
+            return UtilProperties.getMessage(resource, "loginevents.impersonation_disabled", locale);
+        }
+
+        if (!LoginWorker.isUserLoginActive(userLoginToImpersonate)) {
+            Map<String, Object> messageMap = UtilMisc.toMap("username", userLoginIdToImpersonate);
+            return UtilProperties.getMessage(resource, "loginservices.account_for_user_login_id_disabled", messageMap, locale);
+        }
+
+        if (SecurityUtil.hasUserLoginAdminPermission(delegator, userLoginIdToImpersonate)) {
+            return UtilProperties.getMessage(resource, "loginevents.impersonate_notAdmin", locale);
+        }
+
+        if (userLoginIdToImpersonate.equals(userLoginId)) {
+            return UtilProperties.getMessage(resource, "loginevents.impersonate_yourself", locale);
+        }
+
+        //Cannot impersonate more privileged user
+        List<String> missingNeededPermissions = SecurityUtil.hasUserLoginMorePermissionThan(delegator, userLoginId, userLoginIdToImpersonate);
+        if (UtilValidate.isNotEmpty(missingNeededPermissions)) {
+            String missingPermissionListString = missingNeededPermissions.stream().collect(Collectors.joining(", "));
+            return UtilProperties.getMessage(resource, "loginevents.impersonate_notEnoughPermission",
+                    UtilMisc.toMap("missingPermissions", missingPermissionListString), locale);
+        }
+
+        return null;
+    }
+
     public static void createUserLoginPasswordHistory(Delegator delegator,String userLoginId, String currentPassword) throws GenericEntityException{
         int passwordChangeHistoryLimit = 0;
         try {

Modified: ofbiz/ofbiz-framework/trunk/framework/common/webcommon/WEB-INF/common-controller.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/common/webcommon/WEB-INF/common-controller.xml?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/common/webcommon/WEB-INF/common-controller.xml (original)
+++ ofbiz/ofbiz-framework/trunk/framework/common/webcommon/WEB-INF/common-controller.xml Thu Sep 27 16:01:35 2018
@@ -44,6 +44,7 @@ under the License.
         <security https="true" auth="false"/>
         <event type="java" path="org.apache.ofbiz.webapp.control.LoginWorker" invoke="extensionCheckLogin"/>
         <response name="success" type="view" value="main"/>
+        <response name="impersonated" type="view" value="impersonated"/>
         <response name="error" type="view" value="login"/>
     </request-map>
     <request-map uri="ajaxCheckLogin" edit="false">
@@ -53,6 +54,18 @@ under the License.
         <response name="success" type="view" value="main"/>
         <response name="error" type="view" value="ajaxLogin"/>
     </request-map>
+    <request-map uri="impersonateLogin">
+        <security https="true" auth="true"/>
+        <event type="java" path="org.apache.ofbiz.webapp.control.LoginWorker" invoke="impersonateLogin"/>
+        <response name="success" type="view" value="main"/>
+        <response name="error" type="view-last"/>
+    </request-map>
+    <request-map uri="depersonateLogin">
+        <security https="true" auth="true"/>
+        <event type="java" path="org.apache.ofbiz.webapp.control.LoginWorker" invoke="depersonateLogin"/>
+        <response name="success" type="view" value="main"/>
+        <response name="error" type="view-last"/>
+    </request-map>
     <request-map uri="login">
         <security https="true" auth="false"/>
         <event type="java" path="org.apache.ofbiz.webapp.control.LoginWorker" invoke="login"/>
@@ -315,6 +328,7 @@ under the License.
     <view-map name="error" page="/error/error.jsp"/>
     <view-map name="main" type="none"/>
     <view-map name="login" type="screen" page="component://common/widget/CommonScreens.xml#login"/>
+    <view-map name="impersonated" type="screen" page="component://common/widget/CommonScreens.xml#impersonated"/>
     <view-map name="ajaxLogin" type="screen" page="component://common/widget/CommonScreens.xml#ajaxNotLoggedIn"/>
     <view-map name="requirePasswordChange" type="screen" page="component://common/widget/CommonScreens.xml#requirePasswordChange"/>
     <view-map name="forgotPassword" type="screen" page="component://common/widget/CommonScreens.xml#forgotPassword"/>

Modified: ofbiz/ofbiz-framework/trunk/framework/common/widget/CommonScreens.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/common/widget/CommonScreens.xml?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/common/widget/CommonScreens.xml (original)
+++ ofbiz/ofbiz-framework/trunk/framework/common/widget/CommonScreens.xml Thu Sep 27 16:01:35 2018
@@ -229,6 +229,18 @@ under the License.
         </section>
     </screen>
 
+    <screen name="impersonated">
+        <section>
+            <actions>
+                <set field="titleProperty" value="PageTitleImpersonated" />
+            </actions>
+            <widgets>
+                <include-screen name="MinimalActions" />
+                <include-screen name="impersonated" location="${groovy:commonScreenLocations.impersonated?commonScreenLocations.impersonated:commonDecoratorLocation}"/>
+            </widgets>
+        </section>
+    </screen>
+
     <screen name="ajaxNotLoggedIn">
         <section>
             <widgets>

Modified: ofbiz/ofbiz-framework/trunk/framework/security/config/security.properties
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/security/config/security.properties?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/security/config/security.properties (original)
+++ ofbiz/ofbiz-framework/trunk/framework/security/config/security.properties Thu Sep 27 16:01:35 2018
@@ -75,6 +75,11 @@ store.login.history=true
 store.login.history.on.service.auth=false
 store.login.history.incorrect.password=true
 
+# -- disable impersonation
+security.disable.impersonation=false
+# -- if you want that an user cannot operate during an administrator impersonate his account. If true, it can be helpful for QA and dev site
+security.login.authorised.during.impersonate=false
+
 # -- should we encrypt (SHA Hash) the password? --
 password.encrypt=true
 

Modified: ofbiz/ofbiz-framework/trunk/framework/security/data/SecurityGroupDemoData.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/security/data/SecurityGroupDemoData.xml?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/security/data/SecurityGroupDemoData.xml (original)
+++ ofbiz/ofbiz-framework/trunk/framework/security/data/SecurityGroupDemoData.xml Thu Sep 27 16:01:35 2018
@@ -25,7 +25,8 @@ under the License.
     <SecurityGroup groupId="FLEXADMIN" description="Flexible Admin group, has all granular permissions." groupName="Flex Admin"/>
     <SecurityGroup groupId="VIEWADMIN" description="Demo Admin group, has all view permissions." groupName="View Admin"/>
     <SecurityGroup groupId="BIZADMIN" description="Full Business Applications permission group, has all business app admin permissions, not technical permissions." groupName="Biz Admin"/>
-    
+    <SecurityGroup groupId="IMPERSONATION" description="Permission group to impersonate user."/>
+
     <!-- general admin tools permission -->
     <SecurityPermission description="Permission to access the Stock OFBiz Manager Applications." permissionId="OFBTOOLS_VIEW"/>
     <SecurityGroupPermission fromDate="2001-05-13 12:00:00.0" groupId="FULLADMIN" permissionId="OFBTOOLS_VIEW"/>

Modified: ofbiz/ofbiz-framework/trunk/framework/security/entitydef/entitymodel.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/security/entitydef/entitymodel.xml?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/security/entitydef/entitymodel.xml (original)
+++ ofbiz/ofbiz-framework/trunk/framework/security/entitydef/entitymodel.xml Thu Sep 27 16:01:35 2018
@@ -106,11 +106,15 @@ under the License.
       <field name="thruDate" type="date-time"></field>
       <field name="passwordUsed" type="long-varchar" encrypt="true"></field>
       <field name="successfulLogin" type="indicator"></field>
+      <field name="originUserLoginId" type="id-vlong"></field>
       <prim-key field="userLoginId"/>
       <prim-key field="fromDate"/>
       <relation type="one" fk-name="USER_LH_USER" rel-entity-name="UserLogin">
         <key-map field-name="userLoginId"/>
       </relation>
+      <relation type="one-nofk" fk-name="ORIG_USER_LH_USER" rel-entity-name="UserLogin">
+        <key-map field-name="originUserLoginId" rel-field-name="userLoginId"/>
+      </relation>
     </entity>
     <entity entity-name="UserLoginSession"
             package-name="org.apache.ofbiz.security.login"
@@ -176,6 +180,25 @@ under the License.
         <key-map field-name="userLoginId"/>
       </view-link>
     </view-entity>
+    <view-entity entity-name="UserLoginAndPermission"
+                 package-name="org.apache.ofbiz.security.securitygroup"
+                 never-cache="true"
+                 title="UserLogin And Permission View">
+        <member-entity entity-alias="ULSG" entity-name="UserLoginSecurityGroup"/>
+        <member-entity entity-alias="UL" entity-name="UserLogin"/>
+        <member-entity entity-alias="SGP" entity-name="SecurityGroupPermission"/>
+        <alias-all entity-alias="UL"/>
+        <alias-all entity-alias="ULSG"/>
+        <alias name="permissionId" entity-alias="SGP"/>
+        <alias name="permissionFromDate" field="fromDate" entity-alias="SGP"/>
+        <alias name="permissionThruDate" field="thruDate" entity-alias="SGP"/>
+        <view-link entity-alias="ULSG" rel-entity-alias="UL">
+            <key-map field-name="userLoginId"/>
+        </view-link>
+        <view-link entity-alias="ULSG" rel-entity-alias="SGP">
+            <key-map field-name="groupId"/>
+        </view-link>
+    </view-entity>
     <entity entity-name="UserLoginSecurityGroup"
             package-name="org.apache.ofbiz.security.securitygroup"
             title="Security Component - User Login Security Group">

Added: ofbiz/ofbiz-framework/trunk/framework/security/src/doc/asciidoc/_include/security-impersonation.adoc
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/security/src/doc/asciidoc/_include/security-impersonation.adoc?rev=1842110&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/security/src/doc/asciidoc/_include/security-impersonation.adoc (added)
+++ ofbiz/ofbiz-framework/trunk/framework/security/src/doc/asciidoc/_include/security-impersonation.adoc Thu Sep 27 16:01:35 2018
@@ -0,0 +1,127 @@
+////
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+////
+
+= Impersonation
+== What is Impersonation in Apache OFBiz
+The Apache OFBiz Project
+Release 17.12
+
+:imagesdir: ../../themes/common-theme/webapp/images/img/
+ifdef::backend-pdf[]
+:title-logo-image: image::OFBiz-Logo.svg[Apache OFBiz Logo, pdfwidth=4.25in, align=center]
+:source-highlighter: rouge
+endif::[]
+
+=== Introduction to User impersonation
+
+User Impersonation is a feature that offer a way to select a user login and impersonate it, i.e. see what the user could
+see navigating through the application in his name.
+
+=== How do this work ?
+
+An authorized user _(see <<Security,security>> and <<Controls,controls>> section for configuration)_, can select a user
+that will be impersonated.
+
+The impersonation start, if everything is well configured, in current application (partymgr for the demo).
+Everything appears like if we were logged in with the userLoginId and the valid password (though we know nothing about it)
+
+The only thing showing that we currently are impersonating a user is the little bottom-right image :
+
+image::impersonate-ico.png[Impersonate icon, pdfwidth=0.5in, align=right]
+
+This icon indicates, when clicking on it, the user impersonated, and offer a way to depersonate.
+
+The impersonate period is stored for audit purpose, and if the impersonator forgot to depersonate, the period
+is terminated _one hour_ after impersonation start.
+
+=== Security
+
+This feature can draw some concerns about security aspect. This paragraph will introduce every controls and properties
+that have been implemented around the impersonation feature.
+
+[CAUTION]
+These configuration steps are not to be neglected for a *production environment* since this feature offer a way to act
+ in place of another user.
+
+==== Properties
+
+The _security.properties_ file introduce two properties that control impersonation feature :
+
+
+[source]
+security.disable.impersonation = true
+
+This property, set by default to *true*, controls the activation of impersonation feature. If no configuration is done
+any user trying to use impersonation will face an error message, indicating that the feature is disabled.
+
+To enable impersonation this property need to be set to *false*
+
+
+[source]
+security.login.authorised.during.impersonate = false
+
+This property controls the way impersonation occurred to the impersonated user :
+
+In default configuration, the impersonated user see nothing and can use the application without knowing that he is
+currently impersonated. Several authorized user can impersonate a same login without any issue.
+
+[NOTE]
+This configuration is intended for testing/QA environment allowing any authorized user to impersonate a login
+to validate its configuration, test the application etc.
+
+Set to *true*, this configuration improve the control of the data generated by the impersonated user. Indeed, Only one
+authorized user can impersonate a login at the same time, and during the impersonation process, the impersonated user
+is unable to act within the application.
+
+Since the impersonation period is stored in database, the actions done by the
+authorized user can be identified if there is the need to do so.
+[NOTE]
+This configuration is intended for production environment
+
+
+==== Controls
+
+The permission::
+
+First, to be able to use impersonation, a user need to possess _IMPERSONATE_ADMIN_ permissions. Demo data offer
+_IMPERSONATION_ security group for this purpose. +
+In demo data, _FULLADMIN_ security group also possess the permission.
+
+
+Permission based user restriction::
+
+An authorized user cannot impersonate any user. There are two main controls that will restrict the impersonation feature.
+
+Cannot impersonate Admin user:::
+
+It is impossible to impersonate a user that is granted any of the admin permission :
+
+            "IMPERSONATE_ADMIN"
+            "ARTIFACT_INFO_VIEW"
+            "SERVICE_MAINT"
+            "ENTITY_MAINT"
+            "UTIL_CACHE_VIEW"
+            "UTIL_DEBUG_VIEW"
+
+Cannot impersonate more privileged user:::
+
+It is impossible to impersonate a user that has more permission than your user. Even if the missing persmission is
+a minor one.
+
+

Propchange: ofbiz/ofbiz-framework/trunk/framework/security/src/doc/asciidoc/_include/security-impersonation.adoc
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/ofbiz-framework/trunk/framework/security/src/doc/asciidoc/_include/security-impersonation.adoc
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/framework/security/src/doc/asciidoc/_include/security-impersonation.adoc
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ofbiz/ofbiz-framework/trunk/framework/security/src/doc/asciidoc/security.adoc
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/security/src/doc/asciidoc/security.adoc?rev=1842110&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/security/src/doc/asciidoc/security.adoc (added)
+++ ofbiz/ofbiz-framework/trunk/framework/security/src/doc/asciidoc/security.adoc Thu Sep 27 16:01:35 2018
@@ -0,0 +1,23 @@
+////
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+////
+
+= Security
+
+include::_include/security-impersonation.adoc[leveloffset=+1]
+

Propchange: ofbiz/ofbiz-framework/trunk/framework/security/src/doc/asciidoc/security.adoc
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/ofbiz-framework/trunk/framework/security/src/doc/asciidoc/security.adoc
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/framework/security/src/doc/asciidoc/security.adoc
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ofbiz/ofbiz-framework/trunk/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java?rev=1842110&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java (added)
+++ ofbiz/ofbiz-framework/trunk/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java Thu Sep 27 16:01:35 2018
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *******************************************************************************/
+package org.apache.ofbiz.security;
+
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.ofbiz.base.util.Debug;
+import org.apache.ofbiz.base.util.StringUtil;
+import org.apache.ofbiz.base.util.UtilMisc;
+import org.apache.ofbiz.base.util.UtilValidate;
+import org.apache.ofbiz.entity.Delegator;
+import org.apache.ofbiz.entity.GenericEntityException;
+import org.apache.ofbiz.entity.condition.EntityCondition;
+import org.apache.ofbiz.entity.condition.EntityOperator;
+import org.apache.ofbiz.entity.util.EntityQuery;
+import org.apache.ofbiz.entity.util.EntityUtil;
+
+/**
+ * A <code>Security</code> util.
+ */
+public final class SecurityUtil {
+
+    public static final String module = SecurityUtil.class.getName();
+    private static final List<String> adminPermissions = UtilMisc.toList(
+            "IMPERSONATE_ADMIN",
+            "ARTIFACT_INFO_VIEW",
+            "SERVICE_MAINT",
+            "ENTITY_MAINT",
+            "UTIL_CACHE_VIEW",
+            "UTIL_DEBUG_VIEW");
+
+    /**
+     * Return true if given userLogin possess at least one of the adminPermission
+     *
+     * @param delegator
+     * @param userLoginId
+     * @return
+     */
+    public static boolean hasUserLoginAdminPermission(Delegator delegator, String userLoginId) {
+        if (UtilValidate.isEmpty(userLoginId)) return false;
+        try {
+            return EntityQuery.use(delegator)
+                    .from("UserLoginAndPermission")
+                    .where(EntityCondition.makeCondition(
+                            EntityCondition.makeCondition("userLoginId", userLoginId),
+                            EntityCondition.makeCondition("permissionId", EntityOperator.IN, adminPermissions)))
+                    .filterByDate("fromDate", "thruDate", "permissionFromDate", "permissionThruDate")
+                    .queryCount() != 0;
+        } catch (GenericEntityException e) {
+            Debug.logError("Failed to resolve user permissions", module);
+        }
+        return false;
+    }
+
+    /**
+     * Return the list of missing permission, if toUserLoginId has more permission thant userLoginId, emptyList either.
+     *
+     * @param delegator
+     * @param userLoginId
+     * @param toUserLoginId
+     * @return
+     */
+    public static List<String> hasUserLoginMorePermissionThan(Delegator delegator, String userLoginId, String toUserLoginId) {
+        ArrayList<String> returnList = new ArrayList<>();
+        if (UtilValidate.isEmpty(userLoginId) || UtilValidate.isEmpty(toUserLoginId)) return returnList;
+        List<String> userLoginPermissionIds;
+        List<String> toUserLoginPermissionIds;
+        try {
+            userLoginPermissionIds = EntityUtil.getFieldListFromEntityList(
+                    EntityQuery.use(delegator)
+                            .from("UserLoginAndPermission")
+                            .where("userLoginId", userLoginId)
+                            .filterByDate("fromDate", "thruDate", "permissionFromDate", "permissionThruDate")
+                            .queryList(), "permissionId", true);
+            toUserLoginPermissionIds = EntityUtil.getFieldListFromEntityList(
+                    EntityQuery.use(delegator)
+                            .from("UserLoginAndPermission")
+                            .where("userLoginId", toUserLoginId)
+                            .filterByDate("fromDate", "thruDate", "permissionFromDate", "permissionThruDate")
+                            .queryList(), "permissionId", true);
+        } catch (GenericEntityException e) {
+            Debug.logError("Failed to resolve user permissions", module);
+            return returnList;
+        }
+
+        if (UtilValidate.isEmpty(userLoginPermissionIds)) return toUserLoginPermissionIds;
+        if (UtilValidate.isEmpty(toUserLoginPermissionIds)) return returnList;
+
+        //Resolve all ADMIN permissions associated with the origin user
+        List<String> adminPermissions = userLoginPermissionIds.stream()
+                .filter(perm -> perm.endsWith("_ADMIN"))
+                .map(perm -> StringUtil.replaceString(perm, "_ADMIN", ""))
+                .collect(Collectors.toList());
+
+        // if toUserLoginPermissionIds contains at least one permission that is not in admin permission or userLoginPermissionIds
+        // return the list of missing permission
+        return toUserLoginPermissionIds.stream()
+                .filter(perm ->
+                        !userLoginPermissionIds.contains(perm)
+                                && !adminPermissions.contains(perm.substring(0, perm.lastIndexOf("_"))))
+                .collect(Collectors.toList());
+    }
+}
\ No newline at end of file

Propchange: ofbiz/ofbiz-framework/trunk/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/ofbiz-framework/trunk/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginWorker.java
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginWorker.java?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginWorker.java (original)
+++ ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginWorker.java Thu Sep 27 16:01:35 2018
@@ -26,8 +26,10 @@ import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.ServiceLoader;
 import java.util.regex.Matcher;
@@ -59,6 +61,7 @@ import org.apache.ofbiz.base.util.UtilVa
 import org.apache.ofbiz.entity.Delegator;
 import org.apache.ofbiz.entity.DelegatorFactory;
 import org.apache.ofbiz.entity.EntityCryptoException;
+import org.apache.ofbiz.entity.GenericEntity;
 import org.apache.ofbiz.entity.GenericEntityException;
 import org.apache.ofbiz.entity.GenericValue;
 import org.apache.ofbiz.entity.condition.EntityCondition;
@@ -215,6 +218,52 @@ public class LoginWorker {
         return userLogin;
     }
 
+    /**
+     * Return the active {@link GenericValue} of a current impersonation UserLoginHistory of current userLogin session,
+     * only if not the impersonator himself.
+     *
+     * @param request The HTTP request object for the current JSP or Servlet request.
+     * @param response The HTTP response object for the current JSP or Servlet request.
+     * @return GenericValue
+     */
+    public static GenericValue checkImpersonationInProcess(HttpServletRequest request, HttpServletResponse response) {
+        HttpSession session = request.getSession();
+        GenericValue userLogin = (GenericValue) session.getAttribute("userLogin");
+        GenericValue originUserLogin = (GenericValue) session.getAttribute("originUserLogin");
+
+        //if originUserLogin is present, it is the impersonator session
+        if (originUserLogin != null) {
+            return null;
+        }
+
+        //Check the existence of an enabled impersonation visit
+        GenericValue userLoginHistory = null;
+        if (userLogin != null) {
+            try {
+                userLoginHistory = EntityQuery.use(userLogin.getDelegator())
+                        .from("UserLoginHistory")
+                        .where(EntityCondition.makeCondition("userLoginId", userLogin.get("userLoginId")),
+                                EntityCondition.makeCondition("originUserLoginId", EntityOperator.NOT_EQUAL, null))
+                        .filterByDate()
+                        .queryFirst();
+            } catch (GenericEntityException e) {
+                Debug.logError(e, "impossible to resolve userLogin history", module);
+            }
+        }
+        if (userLoginHistory != null) {
+            List<Object> errorMessageList = UtilGenerics.checkList(request.getAttribute("_ERROR_MESSAGE_LIST"));
+            if (errorMessageList == null) {
+                errorMessageList = new LinkedList<>();
+                request.setAttribute("_ERROR_MESSAGE_LIST_", errorMessageList);
+            }
+            HashMap<String, Object> messageMap = new HashMap<>();
+            messageMap.putAll(userLoginHistory.getAllFields());
+            String errMsg = UtilProperties.getMessage(resourceWebapp, "loginevents.impersonation_in_process", messageMap, UtilHttp.getLocale(request));
+            errorMessageList.add(errMsg);
+        }
+        return userLoginHistory;
+    }
+
     /** This WebEvent allows for java 'services' to hook into the login path.
      * This method loads all instances of {@link LoginCheck}, and calls the
      * {@link LoginCheck#associate} method.  The first implementation to return
@@ -315,6 +364,16 @@ public class LoginWorker {
             }
         }
 
+        //Allow loggingOut when impersonated
+        boolean isLoggingOut = "logout".equals(RequestHandler.getRequestUri(request.getPathInfo()));
+        //Check if the user has an impersonation in process
+        boolean authoriseLoginDuringImpersonate = EntityUtilProperties.propertyValueEquals("security", "security.login.authorised.during.impersonate", "true");
+        if (!isLoggingOut && !authoriseLoginDuringImpersonate && checkImpersonationInProcess(request, response) != null) {
+            //remove error message that will be displayed in impersonated status screen
+            request.removeAttribute("_ERROR_MESSAGE_LIST_");
+            return "impersonated";
+        }
+
         return "success";
     }
 
@@ -533,6 +592,156 @@ public class LoginWorker {
         }
     }
 
+    /**
+     * An HTTP WebEvent handler to impersonate a given userLogin without using password. This should run before the security check.
+     *
+     * @param request The HTTP request object for the current JSP or Servlet request.
+     * @param response The HTTP response object for the current JSP or Servlet request.
+     * @return Return a boolean which specifies whether or not the calling Servlet or
+     *         JSP should generate its own content. This allows an event to override the default content.
+     */
+    public static String impersonateLogin(HttpServletRequest request, HttpServletResponse response) {
+        HttpSession session = request.getSession();
+        Delegator delegator = (Delegator) request.getAttribute("delegator");
+        String userLoginIdToImpersonate = request.getParameter("userLoginIdToImpersonate");
+        GenericValue userLogin = (GenericValue) session.getAttribute("userLogin");
+        LocalDispatcher dispatcher;
+
+        if (UtilProperties.getPropertyAsBoolean("security","security.disable.impersonation", true)) {
+            String errMsg = UtilProperties.getMessage(resourceWebapp, "loginevents.impersonation_disabled", UtilHttp.getLocale(request));
+            request.setAttribute("_ERROR_MESSAGE_", errMsg);
+            return "error";
+        }
+
+        //Check if user has impersonate permission
+        Security security = (Security) request.getAttribute("security");
+        if (!security.hasEntityPermission("IMPERSONATE", "_ADMIN", userLogin)) {
+            String errMsg = UtilProperties.getMessage(resourceWebapp, "loginevents.unable_to_login_this_application", UtilHttp.getLocale(request));
+            request.setAttribute("_ERROR_MESSAGE_", errMsg);
+            return "error";
+        }
+
+        List<String> errMsgList = new LinkedList<>();
+        if (UtilValidate.isNotEmpty(session.getAttribute("originUserLogin"))) {
+            errMsgList.add(UtilProperties.getMessage(resourceWebapp, "loginevents.origin_username_is_present", UtilHttp.getLocale(request)));
+        }
+        if (UtilValidate.isEmpty(userLoginIdToImpersonate)) {
+            errMsgList.add(UtilProperties.getMessage(resourceWebapp, "loginevents.username_was_empty_reenter", UtilHttp.getLocale(request)));
+        }
+
+        try {
+            GenericValue userLoginToImpersonate = delegator.findOne("UserLogin", false, "userLoginId", userLoginIdToImpersonate);
+            if (!hasBasePermission(userLoginToImpersonate, request)) {
+                errMsgList.add(UtilProperties.getMessage(resourceWebapp, "loginevents.unable_to_login_this_application", UtilHttp.getLocale(request)));
+            }
+        } catch (GenericEntityException e) {
+            String errMsg ="Error impersonating the userLoginId" + userLoginIdToImpersonate;
+            Debug.logError(e, errMsg, module);
+            errMsgList.add(errMsg);
+            request.setAttribute("_ERROR_MESSAGE_LIST_", errMsgList);
+            return  "error";
+        }
+        if (!errMsgList.isEmpty()) {
+            request.setAttribute("_ERROR_MESSAGE_LIST_", errMsgList);
+            return  "error";
+        }
+
+        ServletContext servletContext = session.getServletContext();
+
+        Debug.logInfo("Setting default delegator", module);
+        String delegatorName = delegator.getDelegatorBaseName();
+        delegator = DelegatorFactory.getDelegator(delegatorName);
+        dispatcher = WebAppUtil.makeWebappDispatcher(servletContext, delegator);
+
+        Map<String, Object> result;
+        try {
+            // get the visit id to pass to the userLogin for history
+            String visitId = VisitHandler.getVisitId(session);
+            result = dispatcher.runSync("userImpersonate",
+                    UtilMisc.toMap("userLoginIdToImpersonate", userLoginIdToImpersonate,
+                            "userLogin", userLogin,"visitId", visitId, "locale", UtilHttp.getLocale(request)));
+        } catch (GenericServiceException e) {
+            Debug.logError(e, "Error calling userImpersonate service", module);
+            Map<String, String> messageMap = UtilMisc.toMap("errorMessage", e.getMessage());
+            String errMsg = UtilProperties.getMessage(resourceWebapp, "loginevents.following_error_occurred_during_login", messageMap, UtilHttp.getLocale(request));
+            request.setAttribute("_ERROR_MESSAGE_", errMsg);
+            return "error";
+        }
+
+        if (ModelService.RESPOND_SUCCESS.equals(result.get(ModelService.RESPONSE_MESSAGE))) {
+            userLogin = (GenericValue) result.get("userLogin");
+            GenericValue originUserLogin = (GenericValue) result.get("originUserLogin");
+
+            Map<String, Object> userLoginSession = checkMap(result.get("userLoginSession"), String.class, Object.class);
+
+            // check on JavaScriptEnabled
+            String javaScriptEnabled = "N";
+            if ("Y".equals(request.getParameter("JavaScriptEnabled"))) {
+                javaScriptEnabled = "Y";
+            }
+            try {
+                dispatcher.runSync("setUserPreference", UtilMisc.toMap("userPrefTypeId", "javaScriptEnabled",
+                        "userPrefGroupTypeId", "GLOBAL_PREFERENCES", "userPrefValue", javaScriptEnabled, "userLogin", userLogin));
+            } catch (GenericServiceException e) {
+                Debug.logError(e, "Error setting user preference", module);
+            }
+
+            //add originUserLogin in session
+            session.setAttribute("originUserLogin", originUserLogin);
+            // finally do the main login routine to set everything else up in the session, etc
+            return doMainLogin(request, response, userLogin, userLoginSession);
+        } else {
+            Map<String, String> messageMap = UtilMisc.toMap("errorMessage", result.get(ModelService.ERROR_MESSAGE));
+            String errMsg = UtilProperties.getMessage(resourceWebapp, "loginevents.following_error_occurred_during_login", messageMap, UtilHttp.getLocale(request));
+            request.setAttribute("_ERROR_MESSAGE_", errMsg);
+            return "error";
+        }
+    }
+
+    /**
+     * An HTTP WebEvent handler to reverse an impersonate login.
+     *
+     * @param request The HTTP request object for the current JSP or Servlet request.
+     * @param response The HTTP response object for the current JSP or Servlet request.
+     * @return Return a boolean which specifies whether or not the calling Servlet or
+     *         JSP should generate its own content. This allows an event to override the default content.
+     */
+    public static String depersonateLogin(HttpServletRequest request, HttpServletResponse response) {
+        HttpSession session = request.getSession();
+        GenericValue originUserLogin = (GenericValue) session.getAttribute("originUserLogin");
+        session.removeAttribute("originUserLogin");
+
+        List<String> errMsgList = new LinkedList<>();
+        if (null == originUserLogin) {
+            errMsgList.add(UtilProperties.getMessage(resourceWebapp, "loginevents.username_was_empty_reenter", UtilHttp.getLocale(request)));
+        }
+        if (!errMsgList.isEmpty()) {
+            request.setAttribute("_ERROR_MESSAGE_LIST_", errMsgList);
+            return "error";
+        }
+
+        //update the userLogin history, only one impersonation of this user can be active at the same time
+        EntityCondition conditions = EntityCondition.makeCondition(
+                EntityCondition.makeCondition("userLoginId", ((GenericValue) session.getAttribute("userLogin")).get("userLoginId")),
+                EntityCondition.makeCondition("originUserLoginId", originUserLogin.get("userLoginId")),
+                EntityUtil.getFilterByDateExpr());
+        try {
+            //check impersonation process existence to avoid depersonation abuse
+            if (EntityQuery.use(originUserLogin.getDelegator()).from("UserLoginHistory").where(conditions).queryCount() == 0) {
+                String errMsg = UtilProperties.getMessage(resourceWebapp, "loginevents.impersonate_NotInProcess", UtilHttp.getLocale(request));
+                request.setAttribute("_ERROR_MESSAGE_", errMsg);
+                return "error";
+            }
+            originUserLogin.getDelegator().storeByCondition("UserLoginHistory",
+                    UtilMisc.toMap("thruDate", UtilDateTime.nowTimestamp()), conditions);
+        } catch (GenericEntityException e) {
+            return "error";
+        }
+
+        // Log back the impersonating user
+        return doMainLogin(request, response, originUserLogin, null);
+    }
+
     protected static void setWebContextObjects(HttpServletRequest request, HttpServletResponse response, Delegator delegator, LocalDispatcher dispatcher) {
         HttpSession session = request.getSession();
         // NOTE: we do NOT want to set this in the servletContext, only in the request and session
@@ -561,6 +770,10 @@ public class LoginWorker {
 
     public static String doMainLogin(HttpServletRequest request, HttpServletResponse response, GenericValue userLogin, Map<String, Object> userLoginSession) {
         HttpSession session = request.getSession();
+        boolean authoriseLoginDuringImpersonate = EntityUtilProperties.propertyValueEquals("security", "security.login.authorised.during.impersonate", "true");
+        if (!authoriseLoginDuringImpersonate && checkImpersonationInProcess(request, response) != null) {
+            return "error";
+        }
         if (userLogin != null && hasBasePermission(userLogin, request)) {
             doBasicLogin(userLogin, request);
         } else {
@@ -1222,4 +1435,13 @@ public class LoginWorker {
         }
         return "success";
     }
+
+    /**
+     * Return true if userLogin has not been disabled
+     * @param userLogin
+     * @return
+     */
+    public static boolean isUserLoginActive(GenericValue userLogin) {
+        return !"N".equals(userLogin.getString("enabled")) && UtilValidate.isEmpty(userLogin.getString("disabledBy"));
+    }
 }

Modified: ofbiz/ofbiz-framework/trunk/themes/bluelight/template/Header.ftl
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/themes/bluelight/template/Header.ftl?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/themes/bluelight/template/Header.ftl (original)
+++ ofbiz/ofbiz-framework/trunk/themes/bluelight/template/Header.ftl Thu Sep 27 16:01:35 2018
@@ -117,6 +117,7 @@ under the License.
 </#if>
 
 <body>
+  <#include "component://common-theme/template/ImpersonateBanner.ftl"/>
   <div id="wait-spinner" style="display:none">
     <div id="wait-spinner-image"></div>
   </div>

Added: ofbiz/ofbiz-framework/trunk/themes/common-theme/template/ImpersonateBanner.ftl
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/themes/common-theme/template/ImpersonateBanner.ftl?rev=1842110&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/themes/common-theme/template/ImpersonateBanner.ftl (added)
+++ ofbiz/ofbiz-framework/trunk/themes/common-theme/template/ImpersonateBanner.ftl Thu Sep 27 16:01:35 2018
@@ -0,0 +1,11 @@
+<#if parameters.originUserLogin??>
+    <a href="#impersonateContent" title="${uiLabelMap.CommonImpersonateTitle}" id="impersonateBtn"><img src="/images/img/impersonate-ico.png" alt="${uiLabelMap.CommonImpersonateTitle}"/></a>
+    <div id="impersonateContent">
+        <div class="impersonateModal">
+            <a href="#" class="btn-close" title="${uiLabelMap.CommonClose}">×</a>
+            <h3>${uiLabelMap.CommonImpersonateTitle}</h3>
+            <p>${uiLabelMap.CommonImpersonateUserLogin} : <strong>${context.userLogin.userLoginId!}</strong></p>
+            <a href="depersonateLogin" class="btn" title="${uiLabelMap.CommonImpersonateStop}">${uiLabelMap.CommonImpersonateStop}</a>
+        </div>
+    </div>
+</#if>

Propchange: ofbiz/ofbiz-framework/trunk/themes/common-theme/template/ImpersonateBanner.ftl
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/ofbiz-framework/trunk/themes/common-theme/template/ImpersonateBanner.ftl
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/themes/common-theme/template/ImpersonateBanner.ftl
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ofbiz/ofbiz-framework/trunk/themes/common-theme/template/Impersonated.ftl
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/themes/common-theme/template/Impersonated.ftl?rev=1842110&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/themes/common-theme/template/Impersonated.ftl (added)
+++ ofbiz/ofbiz-framework/trunk/themes/common-theme/template/Impersonated.ftl Thu Sep 27 16:01:35 2018
@@ -0,0 +1,33 @@
+<#--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<#assign messageMap = Static["org.apache.ofbiz.base.util.UtilMisc"].toMap("originUserLoginId", impersonator)/>
+<#assign impersonationMessage= Static["org.apache.ofbiz.base.util.UtilProperties"].getMessage("SecurityextUiLabels", "loginevents.impersonation_in_process", messageMap, locale)/>
+<#assign fromDate = Static["org.apache.ofbiz.base.util.UtilFormatOut"].formatDateTime(impersonationFromDate, "", locale, timeZone)/>
+<div id="impersonateMode">
+  <div class="content">
+      <img src="/images/img/impersonate-ico.png" alt="${uiLabelMap.CommonImpersonateTitle}"/>
+      <p class="user">${impersonationMessage!}<p>
+      <p>${uiLabelMap.CommonSince} ${fromDate!}</p>
+      <p><a id="logout" class="user-pref-btn" href="<@o...@ofbizUrl>">${uiLabelMap.CommonLogout}</a></p>
+  </div>
+</div>
+<script>
+    setInterval('window.location.reload()', 30000);
+</script>

Propchange: ofbiz/ofbiz-framework/trunk/themes/common-theme/template/Impersonated.ftl
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/ofbiz-framework/trunk/themes/common-theme/template/Impersonated.ftl
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/themes/common-theme/template/Impersonated.ftl
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ofbiz/ofbiz-framework/trunk/themes/common-theme/webapp/common/css/impersonate.css
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/themes/common-theme/webapp/common/css/impersonate.css?rev=1842110&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/themes/common-theme/webapp/common/css/impersonate.css (added)
+++ ofbiz/ofbiz-framework/trunk/themes/common-theme/webapp/common/css/impersonate.css Thu Sep 27 16:01:35 2018
@@ -0,0 +1,180 @@
+#impersonateBtn {
+    position: fixed;
+    bottom: 37px;
+    right: 20px;
+    z-index: 10000;
+    width: 79px;
+    height: 60px;
+    cursor: pointer;
+}
+
+#impersonateBtn img {
+    width: 100%;
+    max-width: 79px;
+    height: auto;
+    opacity: 0.6;
+    transition: opacity .6s;
+}
+
+#impersonateBtn:hover img {
+    opacity: 1;
+}
+
+#impersonateContent:before {
+    content: "";
+    display: none;
+    background: rgba(225, 225, 225, 0.8);
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    z-index: 10;
+}
+
+#impersonateContent:target:before {
+    display: block;
+}
+
+#impersonateContent:target .impersonateModal {
+    opacity: 1;
+    bottom: 117px;
+    right: 20px;
+    -webkit-transform: translate(0, 0);
+    -ms-transform: translate(0, 0);
+    transform: translate(0, 0);
+}
+
+.impersonateModal {
+    opacity: 0;
+    position: fixed;
+    background: #FFF;
+    text-align: center;
+    padding: 30px 30px 15px 30px;
+    border-radius: 5px;
+    z-index: 11;
+    box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.15);
+    -webkit-transform: translate(0, -500%);
+    -ms-transform: translate(0, -500%);
+    transform: translate(0, -500%);
+    -webkit-transition: -webkit-transform 0.3s ease-out;
+    -moz-transition: -moz-transform 0.3s ease-out;
+    -o-transition: -o-transform 0.3s ease-out;
+    transition: transform 0.3s ease-out;
+}
+
+.impersonateModal:before {
+    content: "";
+    position: absolute;
+    bottom: -20px;
+    right: 37px;
+    border: 1em solid #FFF;
+    border-color: transparent transparent #FFF #FFF;
+    transform-origin: 0 0;
+    transform: rotate(-45deg);
+    box-shadow: -3px 3px 5px 0 rgba(0, 0, 0, 0.05);
+}
+
+#impersonateContent .btn-close {
+    color: #aaa;
+    font-size: 25px;
+    text-decoration: none;
+    position: absolute;
+    right: 10px;
+    top: 10px;
+}
+
+#impersonateContent .btn-close:hover {
+    color: #919191;
+}
+
+#impersonateContent h3 {
+    font-size: 26px;
+    font-weight: normal;
+    letter-spacing: 0.5px;
+    line-height: 24px;
+    color: #f08906;
+    border-bottom: 1px solid #F2F2F2;
+    padding-bottom: 10px;
+    margin: 15px 0 20px 0;
+}
+
+#impersonateContent p {
+    font-size: 18px;
+    color: #666;
+    letter-spacing: 0.3px;
+}
+
+#impersonateContent p strong {
+    color: #005982;
+    font-weight: bold;
+}
+
+#impersonateContent .btn {
+    border-radius: 2px;
+    border: 1px solid #005982;
+    padding: 7px 12px;
+    text-transform: uppercase;
+    letter-spacing: 1px;
+    color: #005982;
+    margin: 30px 0 10px 0;
+    line-height: 22px;
+    display: inline-block;
+    -webkit-transition: 0.2s ease-out;
+    -moz-transition: 0.2s ease-out;
+    -o-transition: 0.2s ease-out;
+    transition: 0.2s ease-out;
+}
+
+#impersonateContent .btn:hover {
+    color: #f08906;
+    border-color: #f08906;
+}
+
+#impersonateMode {
+    position: fixed;
+    top: 0;
+    left: 0;
+    height: 100%;
+    min-height: 100vh;
+    width: 100%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    background: rgba(225, 225, 225, 0.8);
+    z-index: 11;
+    cursor: not-allowed;
+}
+
+#impersonateMode .content {
+    background: #FFF;
+    padding: 30px;
+    box-shadow: -3px 3px 5px 0 rgba(0, 0, 0, 0.05);
+    text-align: center;
+}
+
+#impersonateMode .content p.user {
+    margin-bottom: 15px;
+    color: #005982;
+    font-weight: bold;
+}
+
+#impersonateMode .content p {
+    font-size: 18px;
+    color: #666;
+    letter-spacing: 0.3px;
+}
+
+#impersonateMode .content img {
+    margin-bottom: 20px;
+}
+
+#impersonateMode .content .user-pref-btn {
+    width: auto;
+    border-radius: 3px;
+    display: inline-block;
+    padding: 5px 15px;
+    font-size: 15px;
+    text-transform: uppercase;
+    font-weight: 600;
+}

Propchange: ofbiz/ofbiz-framework/trunk/themes/common-theme/webapp/common/css/impersonate.css
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/ofbiz-framework/trunk/themes/common-theme/webapp/common/css/impersonate.css
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/themes/common-theme/webapp/common/css/impersonate.css
------------------------------------------------------------------------------
    svn:mime-type = text/css

Added: ofbiz/ofbiz-framework/trunk/themes/common-theme/webapp/images/img/impersonate-ico.png
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/themes/common-theme/webapp/images/img/impersonate-ico.png?rev=1842110&view=auto
==============================================================================
Binary file - no diff available.

Propchange: ofbiz/ofbiz-framework/trunk/themes/common-theme/webapp/images/img/impersonate-ico.png
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/themes/common-theme/webapp/images/img/impersonate-ico.png
------------------------------------------------------------------------------
    svn:mime-type = image/png

Modified: ofbiz/ofbiz-framework/trunk/themes/common-theme/widget/CommonScreens.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/themes/common-theme/widget/CommonScreens.xml?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/themes/common-theme/widget/CommonScreens.xml (original)
+++ ofbiz/ofbiz-framework/trunk/themes/common-theme/widget/CommonScreens.xml Thu Sep 27 16:01:35 2018
@@ -155,6 +155,7 @@ under the License.
                         <set field="appbarCloseTemplateLocation" from-field="layoutSettings.VT_NAV_CLOSE_TMPLT" />
                         <set field="messagesTemplateLocation" from-field="layoutSettings.VT_MSG_TMPLT_LOC" />
                         <set field="layoutSettings.suppressTab" value="ofbizsetup"/><!-- diseable ofbiz setup by default -->
+                        <set field="layoutSettings.styleSheets[+0]" value="/common/css/impersonate.css" global="true" />
                     </actions>
                     <widgets />
                 </section>
@@ -546,6 +547,42 @@ under the License.
         </section>
     </screen>
 
+    <screen name="impersonated">
+        <section>
+            <actions>
+                <set field="titleProperty" value="PageTitleImpersonated" />
+                <entity-condition list="impersonated" entity-name="UserLoginHistory" filter-by-date="true">
+                    <condition-list>
+                        <condition-list>
+                            <condition-expr field-name="userLoginId" from-field="userLogin.userLoginId"/>
+                            <condition-expr field-name="originUserLoginId" operator="not-equals" from-field="nullField"/>
+                        </condition-list>
+                    </condition-list>
+                    <select-field field-name="originUserLoginId"/>
+                    <select-field field-name="fromDate"/>
+                    <order-by field-name="-fromDate"/>
+                </entity-condition>
+                <set field="impersonator" from-field="impersonated[0].originUserLoginId"/>
+                <set field="impersonationFromDate" from-field="impersonated[0].fromDate"/>
+            </actions>
+            <widgets>
+                <decorator-screen name="main-decorator" location="${parameters.mainDecoratorLocation}">
+                    <decorator-section name="body">
+                        <section>
+                            <actions>
+                                <set field="impersonatedTemplateLocation" from-field="layoutSettings.VT_IMPERSO_TMPLT_LOC" default-value="component://common-theme/template/Impersonated.ftl"/>
+                            </actions>
+                            <widgets />
+                        </section>
+                        <platform-specific>
+                            <html><html-template location="${impersonatedTemplateLocation}"/></html>
+                        </platform-specific>
+                    </decorator-section>
+                </decorator-screen>
+            </widgets>
+        </section>
+    </screen>
+
     <screen name="ajaxNotLoggedIn">
         <section>
             <widgets>
@@ -608,7 +645,7 @@ under the License.
                 <set field="questionEnumId" from-field="securityQuestions[0].questionEnumId" />
                 <entity-one entity-name="Enumeration" value-field="securityQuestion">
                     <field-map field-name="enumId" from-field="questionEnumId"/>
-                </entity-one> 
+                </entity-one>
             </actions>
             <widgets>
                 <decorator-screen name="main-decorator" location="${parameters.mainDecoratorLocation}">

Modified: ofbiz/ofbiz-framework/trunk/themes/common-theme/widget/Theme.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/themes/common-theme/widget/Theme.xml?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/themes/common-theme/widget/Theme.xml (original)
+++ ofbiz/ofbiz-framework/trunk/themes/common-theme/widget/Theme.xml Thu Sep 27 16:01:35 2018
@@ -148,6 +148,7 @@ under the License.
             <screen name="ajaxAutocompleteOptions"/>
             <screen name="FoError"/>
             <screen name="login"/>
+            <screen name="impersonated"/>
             <screen name="ajaxNotLoggedIn"/>
             <screen name="requirePasswordChange"/>
             <screen name="forgotPassword_step1"/>

Modified: ofbiz/ofbiz-framework/trunk/themes/flatgrey/template/Header.ftl
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/themes/flatgrey/template/Header.ftl?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/themes/flatgrey/template/Header.ftl (original)
+++ ofbiz/ofbiz-framework/trunk/themes/flatgrey/template/Header.ftl Thu Sep 27 16:01:35 2018
@@ -97,6 +97,7 @@ under the License.
 </#if>
 <#assign organizationLogoLinkURL = "${layoutSettings.organizationLogoLinkUrl!}">
 <body>
+  <#include "component://common-theme/template/ImpersonateBanner.ftl"/>
   <div id="wait-spinner" style="display:none">
     <div id="wait-spinner-image"></div>
   </div>

Modified: ofbiz/ofbiz-framework/trunk/themes/rainbowstone/template/includes/TopAppBar.ftl
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/themes/rainbowstone/template/includes/TopAppBar.ftl?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/themes/rainbowstone/template/includes/TopAppBar.ftl (original)
+++ ofbiz/ofbiz-framework/trunk/themes/rainbowstone/template/includes/TopAppBar.ftl Thu Sep 27 16:01:35 2018
@@ -30,6 +30,7 @@ under the License.
     </#if>
 </#if>
 <body>
+<#include "component://common-theme/template/ImpersonateBanner.ftl"/>
 <div id="wait-spinner" style="display:none">
     <div id="wait-spinner-image"></div>
 </div>

Modified: ofbiz/ofbiz-framework/trunk/themes/tomahawk/template/Header.ftl
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/themes/tomahawk/template/Header.ftl?rev=1842110&r1=1842109&r2=1842110&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/themes/tomahawk/template/Header.ftl (original)
+++ ofbiz/ofbiz-framework/trunk/themes/tomahawk/template/Header.ftl Thu Sep 27 16:01:35 2018
@@ -102,6 +102,7 @@ under the License.
 <#assign organizationLogoLinkURL = "${layoutSettings.organizationLogoLinkUrl!}">
 
 <body>
+  <#include "component://common-theme/template/ImpersonateBanner.ftl"/>
   <div id="wait-spinner" style="display:none">
     <div id="wait-spinner-image"></div>
   </div>