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.console.wizards.any;
20  
21  import java.util.ArrayList;
22  import java.util.Comparator;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.stream.Collectors;
27  import org.apache.commons.lang3.StringUtils;
28  import org.apache.cxf.jaxrs.ext.search.client.CompleteCondition;
29  import org.apache.syncope.client.console.SyncopeConsoleSession;
30  import org.apache.syncope.client.console.SyncopeWebApplication;
31  import org.apache.syncope.client.console.commons.RealmsUtils;
32  import org.apache.syncope.client.console.rest.GroupRestClient;
33  import org.apache.syncope.client.console.rest.SyncopeRestClient;
34  import org.apache.syncope.client.lib.SyncopeClient;
35  import org.apache.syncope.client.ui.commons.Constants;
36  import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
37  import org.apache.syncope.client.ui.commons.wizards.any.AbstractGroups;
38  import org.apache.syncope.client.ui.commons.wizards.any.AbstractGroupsModel;
39  import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
40  import org.apache.syncope.common.lib.SyncopeConstants;
41  import org.apache.syncope.common.lib.search.GroupFiqlSearchConditionBuilder;
42  import org.apache.syncope.common.lib.to.AnyTO;
43  import org.apache.syncope.common.lib.to.DynRealmTO;
44  import org.apache.syncope.common.lib.to.GroupTO;
45  import org.apache.syncope.common.lib.to.GroupableRelatableTO;
46  import org.apache.syncope.common.lib.to.MembershipTO;
47  import org.apache.syncope.common.lib.types.IdRepoEntitlement;
48  import org.apache.wicket.authroles.authorization.strategies.role.metadata.ActionPermissions;
49  import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
50  import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
51  import org.apache.wicket.markup.html.WebMarkupContainer;
52  import org.apache.wicket.markup.html.basic.Label;
53  import org.apache.wicket.markup.html.form.IChoiceRenderer;
54  import org.apache.wicket.model.IModel;
55  import org.apache.wicket.model.PropertyModel;
56  import org.apache.wicket.model.util.ListModel;
57  import org.apache.wicket.spring.injection.annot.SpringBean;
58  
59  public class Groups extends AbstractGroups {
60  
61      private static final long serialVersionUID = 552437609667518888L;
62  
63      @SpringBean
64      protected GroupRestClient groupRestClient;
65  
66      @SpringBean
67      protected SyncopeRestClient syncopeRestClient;
68  
69      protected final List<DynRealmTO> allDynRealms = new ArrayList<>();
70  
71      protected final boolean templateMode;
72  
73      protected final ConsoleGroupsModel groupsModel;
74  
75      protected AjaxPalettePanel.Builder<MembershipTO> groups;
76  
77      public <T extends AnyTO> Groups(final AnyWrapper<T> modelObject, final boolean templateMode) {
78          super(modelObject);
79          this.templateMode = templateMode;
80          this.groupsModel = new ConsoleGroupsModel();
81  
82          // -----------------------------------------------------------------
83          // Pre-Authorizations
84          // -----------------------------------------------------------------
85          ActionPermissions permissions = new ActionPermissions();
86          setMetaData(MetaDataRoleAuthorizationStrategy.ACTION_PERMISSIONS, permissions);
87          permissions.authorizeAll(RENDER);
88          // -----------------------------------------------------------------
89  
90          addDynamicGroupsContainer();
91          addGroupsPanel();
92          addDynamicRealmsContainer();
93      }
94  
95      protected List<GroupTO> searchAssignable(final String realm, final String term) {
96          return syncopeRestClient.searchAssignableGroups(realm, term, 1, Constants.MAX_GROUP_LIST_SIZE);
97      }
98  
99      @Override
100     protected void addDynamicRealmsContainer() {
101         dynrealmsContainer = new WebMarkupContainer("dynrealmsContainer");
102         dynrealmsContainer.setOutputMarkupId(true);
103         dynrealmsContainer.setOutputMarkupPlaceholderTag(true);
104         dynrealmsContainer.add(new AjaxPalettePanel.Builder<>().build("dynrealms",
105                 new PropertyModel<>(anyTO, "dynRealms"),
106                 new ListModel<>(allDynRealms.stream().map(DynRealmTO::getKey).collect(Collectors.toList()))).
107                 hideLabel().setEnabled(false).setOutputMarkupId(true));
108         add(dynrealmsContainer);
109     }
110 
111     @Override
112     protected void addGroupsPanel() {
113         if (anyTO instanceof GroupTO) {
114             groupsContainer.add(new Label("groups").setVisible(false));
115             groupsContainer.setVisible(false);
116             dyngroupsContainer.add(new Label("dyngroups").setVisible(false));
117             dyngroupsContainer.setVisible(false);
118         } else {
119             groups = new AjaxPalettePanel.Builder<MembershipTO>().setRenderer(new IChoiceRenderer<>() {
120 
121                 private static final long serialVersionUID = -3086661086073628855L;
122 
123                 @Override
124                 public Object getDisplayValue(final MembershipTO object) {
125                     return object.getGroupName();
126                 }
127 
128                 @Override
129                 public String getIdValue(final MembershipTO object, final int index) {
130                     return object.getGroupName();
131                 }
132 
133                 @Override
134                 public MembershipTO getObject(
135                         final String id, final IModel<? extends List<? extends MembershipTO>> choices) {
136 
137                     return choices.getObject().stream().
138                             filter(object -> id.equalsIgnoreCase(object.getGroupName())).findAny().orElse(null);
139                 }
140             });
141 
142             groupsContainer.add(groups.setAllowOrder(true).withFilter("*").build("groups", new ListModel<>() {
143 
144                 private static final long serialVersionUID = -2583290457773357445L;
145 
146                 @Override
147                 public List<MembershipTO> getObject() {
148                     return Groups.this.groupsModel.getMemberships();
149                 }
150             }, new AjaxPalettePanel.Builder.Query<>() {
151 
152                 private static final long serialVersionUID = -7223078772249308813L;
153 
154                 @Override
155                 public List<MembershipTO> execute(final String filter) {
156                     return StringUtils.isEmpty(filter)
157                             ? List.of()
158                             : ("*".equals(filter)
159                                     ? groupsModel.getObject()
160                                     : searchAssignable(anyTO.getRealm(), filter)).stream().
161                                     map(group -> new MembershipTO.Builder(group.getKey()).
162                                     groupName(group.getName()).build()).
163                                     collect(Collectors.toList());
164                 }
165             }).hideLabel().setOutputMarkupId(true));
166 
167             dyngroupsContainer.add(new AjaxPalettePanel.Builder<String>().setAllowOrder(true).build("dyngroups",
168                     new ListModel<>() {
169 
170                 private static final long serialVersionUID = -2583290457773357445L;
171 
172                 @Override
173                 public List<String> getObject() {
174                     return Groups.this.groupsModel.getDynMemberships();
175                 }
176             }, new ListModel<>(groupsModel.getObject().stream().
177                             map(GroupTO::getName).collect(Collectors.toList()))).
178                     hideLabel().setEnabled(false).setOutputMarkupId(true));
179         }
180     }
181 
182     @Override
183     protected void addDynamicGroupsContainer() {
184         dyngroupsContainer = new WebMarkupContainer("dyngroupsContainer");
185         dyngroupsContainer.setOutputMarkupId(true);
186         dyngroupsContainer.setOutputMarkupPlaceholderTag(true);
187         add(dyngroupsContainer);
188     }
189 
190     @Override
191     public boolean evaluate() {
192         return (anyTO instanceof GroupTO
193                 ? !allDynRealms.isEmpty()
194                 : !allDynRealms.isEmpty() || !groupsModel.getObject().isEmpty())
195                 && SyncopeWebApplication.get().getSecuritySettings().getAuthorizationStrategy().
196                         isActionAuthorized(this, RENDER);
197     }
198 
199     public class ConsoleGroupsModel extends AbstractGroupsModel {
200 
201         private static final long serialVersionUID = -4541954630939063927L;
202 
203         protected List<GroupTO> groupsObj;
204 
205         protected List<MembershipTO> membershipsObj;
206 
207         protected List<String> dynMembershipsObj;
208 
209         protected String realmObj;
210 
211         @Override
212         public List<GroupTO> getObject() {
213             reload();
214             return groupsObj;
215         }
216 
217         /**
218          * Retrieve the first MAX_GROUP_LIST_SIZE assignable.
219          */
220         @Override
221         protected void reloadObject() {
222             groupsObj = searchAssignable(realmObj, null);
223         }
224 
225         @Override
226         public List<MembershipTO> getMemberships() {
227             reload();
228             return membershipsObj;
229         }
230 
231         /**
232          * Retrieve group memberships.
233          */
234         @Override
235         protected void reloadMemberships() {
236             // this is to be sure to have group names (required to see membership details in approval page)
237             Map<String, String> assignedGroups = new HashMap<>();
238 
239             int total = GroupableRelatableTO.class.cast(anyTO).getMemberships().size();
240             int pages = (total / Constants.MAX_GROUP_LIST_SIZE) + 1;
241             SortParam<String> sort = new SortParam<>(Constants.NAME_FIELD_NAME, true);
242             for (int page = 1; page <= pages; page++) {
243                 GroupFiqlSearchConditionBuilder builder = SyncopeClient.getGroupSearchConditionBuilder();
244 
245                 List<CompleteCondition> conditions = GroupableRelatableTO.class.cast(anyTO).getMemberships().
246                         stream().
247                         skip((page - 1L) * Constants.MAX_GROUP_LIST_SIZE).
248                         limit(Constants.MAX_GROUP_LIST_SIZE).
249                         map(m -> builder.is(Constants.KEY_FIELD_NAME).equalTo(m.getGroupKey()).wrap()).
250                         collect(Collectors.toList());
251 
252                 if (!conditions.isEmpty()) {
253                     assignedGroups.putAll(groupRestClient.search(
254                             realmObj,
255                             builder.or(conditions).query(),
256                             1,
257                             Constants.MAX_GROUP_LIST_SIZE,
258                             sort,
259                             null).stream().collect(Collectors.toMap(GroupTO::getKey, GroupTO::getName)));
260                 }
261             }
262 
263             // set group names in membership TOs and remove membership not assignable
264             GroupableRelatableTO.class.cast(anyTO).getMemberships().stream().
265                     filter(m -> m.getGroupName() == null && assignedGroups.containsKey(m.getGroupKey())).
266                     forEach(m -> m.setGroupName(assignedGroups.get(m.getGroupKey())));
267             GroupableRelatableTO.class.cast(anyTO).getMemberships().removeIf(m -> m.getGroupName() == null);
268 
269             membershipsObj = GroupableRelatableTO.class.cast(anyTO).getMemberships();
270             membershipsObj.sort(Comparator.comparing(MembershipTO::getGroupName));
271         }
272 
273         @Override
274         public List<String> getDynMemberships() {
275             reload();
276             return dynMembershipsObj;
277         }
278 
279         /**
280          * Retrieve dyn group memberships.
281          */
282         @Override
283         protected void reloadDynMemberships() {
284             GroupFiqlSearchConditionBuilder builder = SyncopeClient.getGroupSearchConditionBuilder();
285 
286             List<CompleteCondition> conditions = GroupableRelatableTO.class.cast(anyTO).getDynMemberships().
287                     stream().map(membership -> builder.is(Constants.KEY_FIELD_NAME).
288                     equalTo(membership.getGroupKey()).wrap()).
289                     collect(Collectors.toList());
290 
291             dynMembershipsObj = new ArrayList<>();
292             if (SyncopeConsoleSession.get().owns(IdRepoEntitlement.GROUP_SEARCH) && !conditions.isEmpty()) {
293                 dynMembershipsObj.addAll(groupRestClient.search(
294                         SyncopeConstants.ROOT_REALM,
295                         builder.or(conditions).query(),
296                         -1,
297                         -1,
298                         new SortParam<>(Constants.NAME_FIELD_NAME, true),
299                         null).stream().map(GroupTO::getName).collect(Collectors.toList()));
300             }
301         }
302 
303         /**
304          * Reload data if the realm changes (see SYNCOPE-1135).
305          */
306         @Override
307         protected void reload() {
308             boolean reload;
309             if (Groups.this.templateMode) {
310                 reload = realmObj == null;
311                 realmObj = SyncopeConstants.ROOT_REALM;
312             } else {
313                 reload = !Groups.this.anyTO.getRealm().equalsIgnoreCase(realmObj);
314                 realmObj = RealmsUtils.getFullPath(Groups.this.anyTO.getRealm());
315             }
316 
317             if (reload) {
318                 reloadObject();
319                 reloadMemberships();
320                 reloadDynMemberships();
321             }
322         }
323     }
324 }