Changeset 8045
- Timestamp:
- Jun 3, 2022, 8:59:50 AM (8 months ago)
- Location:
- trunk
- Files:
-
- 15 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk
- Property svn:mergeinfo changed
/branches/3.19-stable merged: 8031,8033-8042 /tags/3.19.3 (added) merged: 8043
- Property svn:mergeinfo changed
-
trunk/doc/src/docbook/developer/extensions.xml
r7982 r8045 2686 2686 </sect3> 2687 2687 2688 <sect3 id=" pextensions_developer.login-manager.settings">2688 <sect3 id="extensions_developer.login-manager.settings"> 2689 2689 <title>Configuration settings</title> 2690 2690 … … 2747 2747 <para> 2748 2748 All installed and enabled login forms are available in a selection list 2749 from which the user can select to switch to another login form. For technical reasons2750 custom scripts and stylesheets are loaded for all installed login forms even2751 though only one is displayed at a time. Developers must ensure that CSS rules2752 and scripts are not affecting other login forms than the intended one.2749 from which the user can select to switch to another login form. Since BASE 3.19.3 2750 custom scripts and stylesheets are only loaded for the currently active form. 2751 In earlier BASE versions, custom scripts and stylesheets were loaded for all installed 2752 login forms. 2753 2753 </para> 2754 2754 <para> 2755 To help with this, BASE is setting two data-attributes on the 2755 It is possible for both style sheets and scripts to verify that they are only used on 2756 the intended login form. BASE is setting two data-attributes on the 2756 2757 <sgmltag class="starttag">body</sgmltag> tag: 2757 2758 </para> … … 2816 2817 </note> 2817 2818 2819 <sect3 id="extensions_developer.login-form.before-login"> 2820 <title>The before-login event</title> 2821 2822 <para> 2823 In BASE 3.19.3 the <code>before-login</code> was introduced. It is a custom event 2824 that is sent to the <sgmltag class="starttag">form</sgmltag> tag just before the 2825 login form is submitted to the server. Extensions may add event listeners for this 2826 event if they need to take some action and they have the possibility to cancel 2827 the submission by calling the <code>event.preventDefault()</code> method. 2828 </para> 2829 2830 <para> 2831 This functionality is, for example, used by the <ulink 2832 url="https://baseplugins.thep.lu.se/wiki/net.sf.basedb.webauthn">WebAuthn</ulink> extension, 2833 which need to contact the server to get a <emphasis>challenge</emphasis>, ask the user 2834 to insert and click on the security key, and then return the signed challenge in the 2835 <code>extraField</code> before the login form is actually submitted. Since all of this 2836 is handled asynchronously the <code>before-login</code> event need to be cancelled. The login 2837 form can be submitted by calling <code>Login.submitLoginForm()</code>. 2838 </para> 2839 </sect3> 2818 2840 </sect2> 2819 2841 -
trunk/src/clients/web/net/sf/basedb/clients/web/extensions/JspContext.java
r6875 r8045 23 23 24 24 import java.util.Collection; 25 import java.util.HashMap; 25 26 import java.util.HashSet; 27 import java.util.Map; 26 28 import java.util.Set; 27 29 … … 89 91 private final GuiContext guiContext; 90 92 91 private Set<String> scripts; 92 private Set<String> stylesheets; 93 private boolean needResourcesPerExtension; 94 private Map<String, Set<String>> scripts; 95 private Map<String, Set<String>> stylesheets; 93 96 94 97 JspContext(SessionControl sc, DbControl dc, … … 100 103 } 101 104 105 /** 106 Set a flag indicating that resources (eg. scripts and stylesheets) 107 should be tracked per extension. If this is set, it is possible to 108 use {@link #getScripts(String)} and {@link #getStylesheets(String)} 109 to get the resources that was added by the given extension. 110 @since 3.19.3 111 */ 112 public void setNeedResourcesPerExtension(boolean nrpe) 113 { 114 this.needResourcesPerExtension = nrpe; 115 } 116 102 117 /** 103 118 Get the JSP Page context object for the current request. … … 176 191 public void addScript(String absolutePath) 177 192 { 178 if (scripts == null) scripts = new HashSet<String>(); 179 scripts.add(absolutePath); 193 if (scripts == null) scripts = new HashMap<>(); 194 getSet(scripts, null).add(absolutePath); 195 if (needResourcesPerExtension) 196 { 197 getSet(scripts, getCurrentExtension().getId()).add(absolutePath); 198 } 199 } 200 201 /** 202 Get the Set that is stored under the given key. If 203 no Set exists it is created and stored in the map. 204 */ 205 private Set<String> getSet(Map<String, Set<String>> map, String key) 206 { 207 Set<String> set = map.get(key); 208 if (set == null) 209 { 210 set = new HashSet<>(); 211 map.put(key, set); 212 } 213 return set; 180 214 } 181 215 … … 200 234 public void addStylesheet(String absolutePath) 201 235 { 202 if (stylesheets == null) stylesheets = new HashSet<String>(); 203 stylesheets.add(absolutePath); 236 if (stylesheets == null) stylesheets = new HashMap<>(); 237 getSet(stylesheets, null).add(absolutePath); 238 if (needResourcesPerExtension) 239 { 240 getSet(stylesheets, getCurrentExtension().getId()).add(absolutePath); 241 } 204 242 } 205 243 … … 210 248 public Collection<String> getScripts() 211 249 { 212 return scripts; 250 return scripts == null ? null : scripts.get(null); 251 } 252 253 /** 254 Get all scripts that has been added to this context 255 by the given extension 256 @return A collection of scripts 257 @since 3.19.3 258 */ 259 public Collection<String> getScripts(String xtId) 260 { 261 return scripts == null ? null : scripts.get(xtId); 213 262 } 214 263 … … 219 268 public Collection<String> getStylesheets() 220 269 { 221 return stylesheets; 270 return stylesheets == null ? null : stylesheets.get(null); 271 } 272 273 /** 274 Get all stylesheets that has been added to this context 275 by the given extension. 276 @return A collection of stylesheets 277 @since 3.19.3 278 */ 279 public Collection<String> getStylesheets(String xtId) 280 { 281 return stylesheets == null ? null : stylesheets.get(xtId); 222 282 } 223 283 -
trunk/src/clients/web/net/sf/basedb/clients/web/extensions/login/FieldInfo.java
r6426 r8045 22 22 package net.sf.basedb.clients.web.extensions.login; 23 23 24 import net.sf.basedb.clients.web.extensions.DynamicActionAttributeSupport; 25 24 26 /** 25 27 Information about a the login/password fields on the login form. … … 29 31 */ 30 32 public class FieldInfo 33 extends DynamicActionAttributeSupport 31 34 { 32 35 -
trunk/src/clients/web/net/sf/basedb/clients/web/extensions/login/LoginFormBean.java
r7529 r8045 22 22 package net.sf.basedb.clients.web.extensions.login; 23 23 24 import net.sf.basedb.clients.web.extensions.DynamicActionAttributeSupport; 25 24 26 /** 25 27 A simple implementation of the {@link LoginFormAction} interface. … … 29 31 */ 30 32 public class LoginFormBean 33 extends DynamicActionAttributeSupport 31 34 implements LoginFormAction 32 35 { -
trunk/src/clients/web/net/sf/basedb/clients/web/taglib/extensions/Scripts.java
r7703 r8045 63 63 </td> 64 64 </tr> 65 <tr> 66 <td>extension</td> 67 <td>-</td> 68 <td>no</td> 69 <td> 70 If set, only scripts defined by the given extension are 71 included in the output. 72 </td> 73 </tr> 65 74 </table> 66 75 … … 76 85 // The JSP context 77 86 private JspContext context = null; 87 // If set, only output scripts defined by the given extension 88 private String extensionId; 78 89 79 90 public void setContext(JspContext context) 80 91 { 81 92 this.context = context; 93 } 94 95 /** 96 @since 3.19.3 97 */ 98 public void setExtension(String extensionId) 99 { 100 this.extensionId = extensionId; 82 101 } 83 102 … … 88 107 if (context == null) return SKIP_BODY; 89 108 90 Collection<String> scripts = context.getScripts( );109 Collection<String> scripts = context.getScripts(extensionId); 91 110 if (scripts == null) return SKIP_BODY; 92 111 -
trunk/src/clients/web/net/sf/basedb/clients/web/taglib/extensions/Stylesheets.java
r7703 r8045 64 64 </td> 65 65 </tr> 66 <tr> 67 <td>extension</td> 68 <td>-</td> 69 <td>no</td> 70 <td> 71 If set, only scripts defined by the given extension are 72 included in the output. 73 </td> 74 </tr> 66 75 </table> 67 76 … … 77 86 // The JSP context 78 87 private JspContext context = null; 88 // If set, only output scripts defined by the given extension 89 private String extensionId; 79 90 80 91 public void setContext(JspContext context) 81 92 { 82 93 this.context = context; 94 } 95 96 /** 97 @since 3.19.3 98 */ 99 public void setExtension(String extensionId) 100 { 101 this.extensionId = extensionId; 83 102 } 84 103 … … 89 108 if (context == null) return SKIP_BODY; 90 109 91 Collection<String> stylesheets = context.getStylesheets( );110 Collection<String> stylesheets = context.getStylesheets(extensionId); 92 111 if (stylesheets == null) return SKIP_BODY; 93 112 -
trunk/src/core/net/sf/basedb/core/SessionControl.java
r7962 r8045 143 143 */ 144 144 private boolean closed; 145 146 /** 147 Stores name and value of session settings. 148 @since 3.19.3 149 */ 150 private Map<String,Object> sessionSettings; 145 151 146 152 /** … … 188 194 this.currentClient = parent.currentClient; 189 195 this.remoteId = parent.remoteId; 196 this.sessionSettings = parent.sessionSettings; 190 197 this.dbControlCache = Collections.synchronizedMap(new WeakHashMap<DbControl,String>()); 191 198 this.currentContexts = parent.currentContexts; … … 219 226 this.id = id; 220 227 this.remoteId = remoteId; 228 this.sessionSettings = Collections.synchronizedMap(new HashMap<String,Object>()); 221 229 this.dbControlCache = Collections.synchronizedMap(new WeakHashMap<DbControl,String>()); 222 230 this.currentContexts = Collections.synchronizedMap(new HashMap<ContextKey, ItemContext>()); … … 568 576 updateLastAccess(); 569 577 // Are we already logged in? 570 if (isLoggedIn())571 {572 throw new AlreadyLoggedInException(loginInfo.userLogin);573 }574 578 if (loginRequest == null) throw new InvalidUseOfNullException("loginRequest"); 575 579 if (loginRequest.getLogin() == null) throw new InvalidUseOfNullException("loginRequest.login"); 580 if (isLoggedIn() && !loginRequest.isVerifyOnly()) 581 { 582 throw new AlreadyLoggedInException(loginInfo.userLogin); 583 } 576 584 577 585 org.hibernate.Session session = null; … … 609 617 throw new LoginException(ex.getMessage(), ex); 610 618 } 619 620 // Return now if the login request is for verification only 621 if (loginRequest.isVerifyOnly()) return; 611 622 612 623 // The login was ok so far... check device verification … … 1237 1248 li.userName = userData.getName(); 1238 1249 li.authenticationMethod = authenticationMethod; 1239 li.sessionSettings = Collections.synchronizedMap(new HashMap<String,Object>());1240 1250 return li; 1241 1251 } … … 2593 2603 public <T> T getSessionSetting(String name) 2594 2604 { 2595 return loginInfo == null ? null : (T)loginInfo.sessionSettings.get(name);2605 return (T)sessionSettings.get(name); 2596 2606 } 2597 2607 … … 2607 2617 public <T> T setSessionSetting(String name, Object value) 2608 2618 { 2609 if (loginInfo == null || loginInfo.sessionSettings == null) return null;2610 2619 if (value == null) 2611 2620 { 2612 return (T) loginInfo.sessionSettings.remove(name);2621 return (T)sessionSettings.remove(name); 2613 2622 } 2614 2623 else 2615 2624 { 2616 return (T) loginInfo.sessionSettings.put(name, value);2625 return (T)sessionSettings.put(name, value); 2617 2626 } 2618 2627 } … … 2938 2947 private Map<String,SettingInfo> userDefaultSettings; 2939 2948 2940 /**2941 Stores name and value of session settings.2942 */2943 private Map<String,Object> sessionSettings;2944 2945 2949 private LoginInfo() 2946 2950 {} … … 2961 2965 this.userClientSettings = parent.userClientSettings; 2962 2966 this.userDefaultSettings = parent.userDefaultSettings; 2963 this.sessionSettings = parent.sessionSettings;2964 2967 } 2965 2968 -
trunk/src/core/net/sf/basedb/core/authentication/LoginRequest.java
r7408 r8045 24 24 import java.util.HashMap; 25 25 import java.util.Map; 26 27 import net.sf.basedb.core.SessionControl; 26 28 27 29 /** … … 34 36 public class LoginRequest 35 37 { 36 38 private boolean verifyOnly; 37 39 private int userId; 38 40 private String login; … … 86 88 } 87 89 90 /** 91 Set a flag to indicate if the login request should only verify 92 the login parameters and not do a full login. 93 @since 3.19.3 94 */ 95 public void setVerifyOnly(boolean verifyOnly) 96 { 97 this.verifyOnly = verifyOnly; 98 } 99 100 /** 101 If this flag is set, the login parameters are only verified and 102 the actual login is aborted. If the verification is successful, the 103 {@link SessionControl#login(LoginRequest)} returns normally, otherwise 104 an exception is thrown. 105 @since 3.19.3 106 */ 107 public boolean isVerifyOnly() 108 { 109 return verifyOnly; 110 } 88 111 89 112 /** -
trunk/src/core/net/sf/basedb/util/extensions/ClientContext.java
r7895 r8045 69 69 private final SessionControl sc; 70 70 private final DbControl dc; 71 private ExtensionPoint<?> currentXtPoint; 72 private Extension<?> currentXt; 71 73 private Object item; 72 74 private Map<String, Object> attributes; … … 173 175 { 174 176 this.item = item; 177 } 178 179 /** 180 Get the currently active extension point. 181 @since 3.19.3 182 */ 183 public ExtensionPoint<?> getCurrentExtensionPoint() 184 { 185 return currentXtPoint; 186 } 187 protected void setCurrentExtensionPoint(ExtensionPoint<?> xtPoint) 188 { 189 this.currentXtPoint = xtPoint; 190 } 191 192 /** 193 Get the currently active extension point. 194 @since 3.19.3 195 */ 196 public Extension<?> getCurrentExtension() 197 { 198 return currentXt; 199 } 200 protected void setCurrentExtension(Extension<?> xt) 201 { 202 this.currentXt = xt; 175 203 } 176 204 -
trunk/src/core/net/sf/basedb/util/extensions/Registry.java
r7642 r8045 673 673 674 674 // YES! ... 675 if (clientContext != null) clientContext.setCurrentExtensionPoint(rep); 675 676 ErrorHandlerFactory ehf = rep.getErrorHandlerFactory(); 676 677 ExtensionPointContext<Action> mainContext = new ExtensionPointContext<Action>(this, … … 690 691 691 692 // Create invokation context for the extension 693 if (clientContext != null) clientContext.setCurrentExtension(ext); 692 694 ExtensionContext<A> context = 693 695 new ExtensionContext(mainContext, ext); -
trunk/www/WEB-INF/extensions.tld
r7604 r8045 41 41 <rtexprvalue>true</rtexprvalue> 42 42 </attribute> 43 <attribute> 44 <name>extension</name> 45 <required>false</required> 46 <rtexprvalue>true</rtexprvalue> 47 </attribute> 43 48 </tag> 44 49 … … 50 55 <name>context</name> 51 56 <required>true</required> 57 <rtexprvalue>true</rtexprvalue> 58 </attribute> 59 <attribute> 60 <name>extension</name> 61 <required>false</required> 52 62 <rtexprvalue>true</rtexprvalue> 53 63 </attribute> -
trunk/www/login.js
r7815 r8045 305 305 } 306 306 } 307 308 // Send custom event to let extensions do stuff before submitting 309 Doc.hide('login-error'); 310 var evt = new CustomEvent('before-login', { cancelable: true, bubbles: true }); 311 if (frm.dispatchEvent(evt)) 312 { 313 login.submitLoginForm(); 314 } 315 } 316 317 /** 318 Submit the login form with no questions asked. 319 */ 320 login.submitLoginForm = function() 321 { 322 var frm = document.forms['login']; 307 323 if (!pUseLastLogin) 308 324 { … … 317 333 frm.submit(); 318 334 } 319 320 335 } 321 336 -
trunk/www/main.jsp
r7954 r8045 52 52 import="net.sf.basedb.clients.web.extensions.login.FieldInfo" 53 53 import="net.sf.basedb.clients.web.extensions.login.PasswordLoginFormFactory" 54 import="net.sf.basedb.clients.web.extensions.DynamicActionAttributeSupport" 54 55 import="net.sf.basedb.util.Values" 55 56 import="java.util.Date" … … 87 88 ItemResultIterator<News> news = null; 88 89 JspContext jspContext = ExtensionsControl.createContext(dc, pageContext); 90 jspContext.setNeedResourcesPerExtension(true); 89 91 ExtensionsInvoker<LoginFormAction> invoker = ExtensionsControl.useExtensions(jspContext, "net.sf.basedb.clients.web.login-form"); 90 92 91 93 LoginFormAction loginAction = null; 92 94 String selectedLoginForm = null; 95 String selectedExtension = null; 93 96 Map<String, String> allForms = new TreeMap<String, String>(); 94 97 … … 109 112 loginAction = action; 110 113 selectedLoginForm = formId; 114 selectedExtension = it.getExtension().getId(); 111 115 } 112 116 } … … 124 128 <base:page type="default"> 125 129 <base:head styles="login.css" scripts="~login.js"> 126 <ext:scripts context="<%=jspContext%>" />127 <ext:stylesheets context="<%=jspContext%>" />130 <ext:scripts context="<%=jspContext%>" extension="<%=selectedExtension%>" /> 131 <ext:stylesheets context="<%=jspContext%>" extension="<%=selectedExtension%>" /> 128 132 </base:head> 129 133 <base:body style="padding-top: 5em;" data-login-form="<%=HTML.encodeTags(selectedLoginForm)%>" data-requested-form="<%=HTML.encodeTags(requestedLoginForm) %>"> 130 <form name="login" action="login.jsp" method="post"> 134 <form name="login" action="login.jsp" method="post" 135 <%=DynamicActionAttributeSupport.getAttributesString(loginAction)%> 136 > 131 137 <input type="hidden" name="ID" value="<%=ID%>"> 132 138 <input type="hidden" name="again" value="<%=again?1:0%>"> … … 171 177 <% 172 178 } 173 if (error != null)174 {175 %>176 <div class="messagecontainer error" style="margin-top: 1em; margin-bottom: 1em;"><%=error%></div>177 <%178 }179 179 %> 180 <div id="login-error" class="messagecontainer error" 181 style="margin-top: 1em; margin-bottom: 1em;<%=error==null?"display:none;":""%>"> 182 <%=error==null?"":error%> 183 </div> 180 184 <table style="width: 100%; border-collapse: separate;"> 181 185 <tr> … … 195 199 <%=loginAction.rememberLastLogin() ? "" : "autocomplete=\"off\" data-use-last-login=\"0\""%> 196 200 maxlength="100" 197 tabindex="1"> 201 tabindex="1" 202 <%=DynamicActionAttributeSupport.getAttributesString(loginField)%> 203 > 198 204 </td> 199 205 </tr> … … 207 213 <%=valueIfNotNull("placeholder=\"", passwordField.getPlaceHolder(), "\"") %> 208 214 maxlength="80" 209 tabindex="2"> 215 tabindex="2" 216 <%=DynamicActionAttributeSupport.getAttributesString(passwordField)%> 217 > 210 218 </td> 211 219 <td <%=extraField != null?"rowspan=\"2\"" : "" %> style="vertical-align: bottom;"> … … 237 245 <%=valueIfNotNull("placeholder=\"", extraField.getPlaceHolder(), "\"") %> 238 246 maxlength="80" 239 tabindex="3"> 247 tabindex="3" 248 <%=DynamicActionAttributeSupport.getAttributesString(extraField)%> 249 > 240 250 </td> 241 251 </tr> -
trunk/www/switch.jsp
r7954 r8045 38 38 import="net.sf.basedb.clients.web.extensions.ExtensionsControl" 39 39 import="net.sf.basedb.clients.web.extensions.JspContext" 40 import="net.sf.basedb.clients.web.extensions.DynamicActionAttributeSupport" 40 41 import="net.sf.basedb.clients.web.extensions.login.LoginFormAction" 41 42 import="net.sf.basedb.clients.web.extensions.login.PasswordLoginFormFactory" … … 64 65 { 65 66 JspContext jspContext = ExtensionsControl.createContext(dc, pageContext); 67 jspContext.setNeedResourcesPerExtension(true); 66 68 ExtensionsInvoker<LoginFormAction> invoker = ExtensionsControl.useExtensions(jspContext, "net.sf.basedb.clients.web.login-form"); 67 69 68 70 LoginFormAction loginAction = null; 69 71 String selectedLoginForm = null; 72 String selectedExtension = null; 70 73 Map<String, String> allForms = new TreeMap<String, String>(); 71 74 … … 86 89 loginAction = action; 87 90 selectedLoginForm = formId; 91 selectedExtension = it.getExtension().getId(); 88 92 } 89 93 } … … 100 104 <base:page type="popup" title="Switch user"> 101 105 <base:head styles="login.css" scripts="~login.js"> 102 <ext:scripts context="<%=jspContext%>" />103 <ext:stylesheets context="<%=jspContext%>" />106 <ext:scripts context="<%=jspContext%>" extension="<%=selectedExtension%>" /> 107 <ext:stylesheets context="<%=jspContext%>" extension="<%=selectedExtension%>" /> 104 108 </base:head> 105 109 <base:body data-login-form="<%=HTML.encodeTags(selectedLoginForm)%>" data-requested-form="<%=HTML.encodeTags(requestedLoginForm) %>"> 106 110 <h1>Switch user <base:help helpid="switchuser" /></h1> 107 <form name="login" action="login.jsp" method="post"> 111 <form name="login" action="login.jsp" method="post" 112 <%=DynamicActionAttributeSupport.getAttributesString(loginAction)%> 113 > 108 114 <input type="hidden" name="ID" value="<%=ID%>"> 109 115 <input type="hidden" name="again" value="1"> … … 148 154 data-use-last-login="0" 149 155 maxlength="100" 150 tabindex="0"> 156 tabindex="0" 157 <%=DynamicActionAttributeSupport.getAttributesString(loginField)%> 158 > 151 159 </td> 152 160 </tr> … … 159 167 <%=valueIfNotNull("placeholder=\"", passwordField.getPlaceHolder(), "\"") %> 160 168 maxlength="80" 161 tabindex="0"> 169 tabindex="0" 170 <%=DynamicActionAttributeSupport.getAttributesString(passwordField)%> 171 > 162 172 </td> 163 173 </tr> … … 174 184 <%=valueIfNotNull("placeholder=\"", extraField.getPlaceHolder(), "\"") %> 175 185 maxlength="80" 176 tabindex="0"> 186 tabindex="0" 187 <%=DynamicActionAttributeSupport.getAttributesString(extraField)%> 188 > 177 189 </td> 178 190 </tr> … … 194 206 <%=loginAction.getHelp() %> 195 207 </div> 208 <div id="login-error" class="messagecontainer error" 209 style="margin-top: 1em; margin-bottom: 1em; display:none;"> 210 </div> 196 211 </div> 197 212 </td> … … 204 219 <tr class="dynamic"> 205 220 <th></th> 206 <td></td> 221 <td> 222 <div id="login-error" class="messagecontainer error" 223 style="margin-top: 1em; margin-bottom: 1em; display:none;"> 224 </div> 225 </td> 207 226 </tr> 208 227 <%
Note: See TracChangeset
for help on using the changeset viewer.