View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.client.ui.commons;
20  
21  import com.googlecode.wicket.kendo.ui.widget.notification.Notification;
22  import java.security.AccessControlException;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.List;
26  import java.util.Locale;
27  import java.util.stream.Collectors;
28  import javax.ws.rs.core.HttpHeaders;
29  import org.apache.commons.lang3.StringUtils;
30  import org.apache.syncope.client.ui.commons.panels.NotificationPanel;
31  import org.apache.syncope.common.keymaster.client.api.DomainOps;
32  import org.apache.syncope.common.keymaster.client.api.model.Domain;
33  import org.apache.syncope.common.lib.SyncopeConstants;
34  import org.apache.wicket.Component;
35  import org.apache.wicket.ajax.AjaxRequestTarget;
36  import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
37  import org.apache.wicket.ajax.markup.html.form.AjaxButton;
38  import org.apache.wicket.markup.head.IHeaderResponse;
39  import org.apache.wicket.markup.head.OnLoadHeaderItem;
40  import org.apache.wicket.markup.html.WebPage;
41  import org.apache.wicket.markup.html.basic.Label;
42  import org.apache.wicket.markup.html.form.ChoiceRenderer;
43  import org.apache.wicket.markup.html.form.DropDownChoice;
44  import org.apache.wicket.markup.html.form.PasswordTextField;
45  import org.apache.wicket.markup.html.form.StatelessForm;
46  import org.apache.wicket.markup.html.form.TextField;
47  import org.apache.wicket.markup.html.list.ListItem;
48  import org.apache.wicket.markup.html.list.ListView;
49  import org.apache.wicket.markup.html.panel.Panel;
50  import org.apache.wicket.model.IModel;
51  import org.apache.wicket.model.LoadableDetachableModel;
52  import org.apache.wicket.model.Model;
53  import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
54  import org.apache.wicket.request.cycle.RequestCycle;
55  import org.apache.wicket.request.mapper.parameter.PageParameters;
56  import org.apache.wicket.spring.injection.annot.SpringBean;
57  import org.slf4j.Logger;
58  import org.slf4j.LoggerFactory;
59  
60  public abstract class BaseLogin extends WebPage {
61  
62      private static final long serialVersionUID = 5889157642852559004L;
63  
64      protected static final Logger LOG = LoggerFactory.getLogger(BaseLogin.class);
65  
66      @SpringBean
67      protected DomainOps domainOps;
68  
69      protected final NotificationPanel notificationPanel;
70  
71      protected final StatelessForm<Void> form;
72  
73      protected final TextField<String> usernameField;
74  
75      protected final TextField<String> passwordField;
76  
77      protected String notificationMessage;
78  
79      protected String notificationLevel;
80  
81      protected final LoadableDetachableModel<List<String>> domains = new LoadableDetachableModel<>() {
82  
83          private static final long serialVersionUID = 4659376149825914247L;
84  
85          @Override
86          protected List<String> load() {
87              List<String> current = new ArrayList<>();
88              current.addAll(domainOps.list().stream().map(Domain::getKey).sorted().collect(Collectors.toList()));
89              current.add(0, SyncopeConstants.MASTER_DOMAIN);
90              return current;
91          }
92      };
93  
94      public BaseLogin(final PageParameters parameters) {
95          super(parameters);
96          setStatelessHint(true);
97  
98          notificationPanel = new NotificationPanel(Constants.FEEDBACK);
99          add(notificationPanel);
100 
101         if (!parameters.get("notificationMessage").isNull()) {
102             notificationMessage = parameters.get(Constants.NOTIFICATION_MSG_PARAM).toString();
103             notificationLevel = parameters.get(Constants.NOTIFICATION_LEVEL_PARAM).isEmpty()
104                     ? Notification.SUCCESS
105                     : parameters.get(Constants.NOTIFICATION_LEVEL_PARAM).toString();
106         }
107 
108         Label exceptionMessage = new Label("exceptionMessage");
109         exceptionMessage.setOutputMarkupPlaceholderTag(true);
110         exceptionMessage.setVisible(false);
111         if (!parameters.get("errorMessage").isNull()) {
112             exceptionMessage.setVisible(true);
113             exceptionMessage.setDefaultModel(Model.of(parameters.get("errorMessage")));
114         }
115         add(exceptionMessage);
116 
117         form = new StatelessForm<>("login");
118 
119         usernameField = new TextField<>("username", new Model<>());
120         usernameField.setMarkupId("username");
121         form.add(usernameField);
122 
123         passwordField = new PasswordTextField("password", new Model<>());
124         passwordField.setMarkupId("password");
125         form.add(passwordField);
126 
127         LocaleDropDown languageSelect = new LocaleDropDown("language");
128         languageSelect.add(new AjaxFormComponentUpdatingBehavior(Constants.ON_BLUR) {
129 
130             private static final long serialVersionUID = -1107858522700306810L;
131 
132             @Override
133             protected void onUpdate(final AjaxRequestTarget target) {
134                 getLanguageOnChangeComponents().forEach(target::add);
135             }
136         }).add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
137 
138             private static final long serialVersionUID = -1107858522700306810L;
139 
140             @Override
141             protected void onUpdate(final AjaxRequestTarget target) {
142                 getLanguageOnChangeComponents().forEach(target::add);
143             }
144         });
145         form.add(languageSelect.setOutputMarkupId(true));
146 
147         DomainDropDown domainSelect = new DomainDropDown("domain", domains);
148         domainSelect.add(new AjaxFormComponentUpdatingBehavior(Constants.ON_BLUR) {
149 
150             private static final long serialVersionUID = -1107858522700306810L;
151 
152             @Override
153             protected void onUpdate(final AjaxRequestTarget target) {
154                 getDomainOnChangeComponents().forEach(target::add);
155             }
156         }).add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
157 
158             private static final long serialVersionUID = -1107858522700306810L;
159 
160             @Override
161             protected void onUpdate(final AjaxRequestTarget target) {
162                 getDomainOnChangeComponents().forEach(target::add);
163             }
164         });
165         form.add(domainSelect.setOutputMarkupId(true));
166 
167         AjaxButton submitButton = new AjaxButton("submit", new Model<>(getString("submit"))) {
168 
169             private static final long serialVersionUID = 429178684321093953L;
170 
171             @Override
172             protected void onSubmit(final AjaxRequestTarget target) {
173                 authenticate(usernameField.getRawInput(), passwordField.getRawInput(), target);
174             }
175 
176         };
177         submitButton.setDefaultFormProcessing(false);
178         form.add(submitButton);
179         form.setDefaultButton(submitButton);
180 
181         List<Panel> ssoLoginFormPanels = getSSOLoginFormPanels();
182         ListView<Panel> ssoLogins = new ListView<>("ssoLogins", ssoLoginFormPanels) {
183 
184             private static final long serialVersionUID = -9180479401817023838L;
185 
186             @Override
187             protected void populateItem(final ListItem<Panel> item) {
188                 item.add(item.getModelObject());
189             }
190         };
191         form.add(ssoLogins);
192 
193         add(form);
194     }
195 
196     protected Collection<Component> getLanguageOnChangeComponents() {
197         return List.of(form);
198     }
199 
200     protected Collection<Component> getDomainOnChangeComponents() {
201         return List.of(form);
202     }
203 
204     @Override
205     public void renderHead(final IHeaderResponse response) {
206         super.renderHead(response);
207         if (StringUtils.isNotBlank(notificationMessage)) {
208             response.render(OnLoadHeaderItem.forScript(StyledNotificationBehavior.jQueryShow(notificationMessage,
209                     String.format("jQuery('#%s').data('kendoNotification')",
210                             notificationPanel.getNotificationMarkupId()), notificationLevel)));
211         }
212     }
213 
214     protected abstract BaseSession getBaseSession();
215 
216     protected abstract List<Panel> getSSOLoginFormPanels();
217 
218     protected abstract void sendError(String error);
219 
220     protected abstract void authenticate(
221             String username,
222             String password,
223             AjaxRequestTarget target)
224             throws AccessControlException;
225 
226     /**
227      * Inner class which implements (custom) Locale DropDownChoice component.
228      */
229     protected class LocaleDropDown extends DropDownChoice<Locale> {
230 
231         private static final long serialVersionUID = 2349382679992357202L;
232 
233         protected class LocaleRenderer extends ChoiceRenderer<Locale> {
234 
235             private static final long serialVersionUID = -3657529581555164741L;
236 
237             @Override
238             public String getDisplayValue(final Locale locale) {
239                 return locale.getDisplayName(getLocale());
240             }
241         }
242 
243         protected LocaleDropDown(final String id) {
244             super(id, getBaseSession().getSupportedLocales());
245 
246             setChoiceRenderer(new LocaleRenderer());
247             setModel(new IModel<>() {
248 
249                 private static final long serialVersionUID = -6985170095629312963L;
250 
251                 @Override
252                 public Locale getObject() {
253                     return getSession().getLocale();
254                 }
255 
256                 @Override
257                 public void setObject(final Locale object) {
258                     getSession().setLocale(object);
259                 }
260 
261                 @Override
262                 public void detach() {
263                     // Empty.
264                 }
265             });
266 
267             // set default language selection
268             List<Locale> filtered = List.of();
269 
270             String acceptLanguage = ((ServletWebRequest) RequestCycle.get().getRequest()).
271                     getHeader(HttpHeaders.ACCEPT_LANGUAGE);
272             if (StringUtils.isNotBlank(acceptLanguage)) {
273                 try {
274                     filtered = Locale.filter(
275                             Locale.LanguageRange.parse(acceptLanguage),
276                             getBaseSession().getSupportedLocales());
277                 } catch (Exception e) {
278                     LOG.debug("Could not parse {} HTTP header value '{}'",
279                             HttpHeaders.ACCEPT_LANGUAGE, acceptLanguage, e);
280                 }
281             }
282 
283             getModel().setObject(filtered.isEmpty()
284                     ? Locale.ENGLISH
285                     : filtered.get(0));
286         }
287     }
288 }