1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package edu.internet2.middleware.shibboleth.common.attribute;
18
19 import jargs.gnu.CmdLineParser;
20
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.IOException;
24 import java.io.PrintStream;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.Map;
28
29 import org.opensaml.Configuration;
30 import org.opensaml.common.SAMLObject;
31 import org.opensaml.saml2.metadata.provider.MetadataProvider;
32 import org.opensaml.util.resource.FilesystemResource;
33 import org.opensaml.util.resource.Resource;
34 import org.opensaml.util.resource.ResourceException;
35 import org.opensaml.xml.io.Marshaller;
36 import org.opensaml.xml.io.MarshallingException;
37 import org.opensaml.xml.util.XMLHelper;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40 import org.springframework.context.ApplicationContext;
41 import org.springframework.context.support.GenericApplicationContext;
42 import org.w3c.dom.Element;
43
44 import ch.qos.logback.classic.LoggerContext;
45 import ch.qos.logback.classic.joran.JoranConfigurator;
46 import ch.qos.logback.core.joran.spi.JoranException;
47 import ch.qos.logback.core.status.ErrorStatus;
48 import ch.qos.logback.core.status.InfoStatus;
49 import ch.qos.logback.core.status.StatusManager;
50
51 import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML1AttributeAuthority;
52 import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML2AttributeAuthority;
53 import edu.internet2.middleware.shibboleth.common.config.SpringConfigurationUtils;
54 import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
55 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
56
57
58
59
60
61
62
63
64
65 public class AttributeAuthorityCLI {
66
67
68 private static Logger log = LoggerFactory.getLogger(AttributeAuthorityCLI.class);
69
70
71 private static String[] aacliConfigs = { "/internal.xml", "/service.xml", };
72
73
74 private static SAML1AttributeAuthority saml1AA;
75
76
77 private static SAML2AttributeAuthority saml2AA;
78
79
80
81
82
83
84
85
86 public static void main(String[] args) throws Exception {
87 CmdLineParser parser = parseCommandArguments(args);
88 ApplicationContext appCtx = loadConfigurations((String) parser.getOptionValue(CLIParserBuilder.CONFIG_DIR_ARG));
89
90 saml1AA = (SAML1AttributeAuthority) appCtx.getBean("shibboleth.SAML1AttributeAuthority");
91 saml2AA = (SAML2AttributeAuthority) appCtx.getBean("shibboleth.SAML2AttributeAuthority");
92
93 SAMLObject attributeStatement;
94 Boolean saml1 = (Boolean) parser.getOptionValue(CLIParserBuilder.SAML1_ARG, Boolean.FALSE);
95 if (saml1.booleanValue()) {
96 attributeStatement = performSAML1AttributeResolution(parser, appCtx);
97 } else {
98 attributeStatement = performSAML2AttributeResolution(parser, appCtx);
99 }
100
101 printAttributeStatement(attributeStatement);
102 }
103
104
105
106
107
108
109
110
111
112
113 private static CmdLineParser parseCommandArguments(String[] args) throws Exception {
114 if (args.length < 2) {
115 printHelp(System.out);
116 System.out.flush();
117 System.exit(0);
118 }
119
120 CmdLineParser parser = CLIParserBuilder.buildParser();
121
122 try {
123 parser.parse(args);
124 } catch (CmdLineParser.OptionException e) {
125 errorAndExit(e.getMessage(), e);
126 }
127
128 Boolean helpEnabled = (Boolean) parser.getOptionValue(CLIParserBuilder.HELP_ARG);
129 if (helpEnabled != null) {
130 printHelp(System.out);
131 System.out.flush();
132 System.exit(0);
133 }
134
135 return parser;
136 }
137
138
139
140
141
142
143
144
145
146
147
148 private static ApplicationContext loadConfigurations(String configDir) throws IOException, ResourceException {
149 File configDirectory;
150
151 if (configDir != null) {
152 configDirectory = new File(configDir);
153 } else {
154 configDirectory = new File(System.getenv("IDP_HOME") + "/conf");
155 }
156
157 if (!configDirectory.exists() || !configDirectory.isDirectory() || !configDirectory.canRead()) {
158 errorAndExit("Configuration directory " + configDir
159 + " does not exist, is not a directory, or is not readable", null);
160 }
161
162 loadLoggingConfiguration(configDirectory.getAbsolutePath());
163
164 List<Resource> configs = new ArrayList<Resource>();
165
166 File config;
167 for (int i = 0; i < aacliConfigs.length; i++) {
168 config = new File(configDirectory.getPath() + aacliConfigs[i]);
169 if (config.isDirectory() || !config.canRead()) {
170 errorAndExit("Configuration file " + config.getAbsolutePath() + " is a directory or is not readable",
171 null);
172 }
173 configs.add(new FilesystemResource(config.getPath()));
174 }
175
176 GenericApplicationContext gContext = new GenericApplicationContext();
177 SpringConfigurationUtils.populateRegistry(gContext, configs);
178 gContext.refresh();
179 return gContext;
180 }
181
182
183
184
185
186
187
188 private static void loadLoggingConfiguration(String configDir) {
189 String loggingConfig = configDir + File.pathSeparator + "logging.xml";
190
191 LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
192 StatusManager statusManager = loggerContext.getStatusManager();
193 statusManager.add(new InfoStatus("Loading logging configuration file: " + loggingConfig, null));
194 try {
195 loggerContext.shutdownAndReset();
196 JoranConfigurator configurator = new JoranConfigurator();
197 configurator.setContext(loggerContext);
198 configurator.doConfigure(new FileInputStream(loggingConfig));
199 loggerContext.start();
200 } catch (JoranException e) {
201 statusManager.add(new ErrorStatus("Error loading logging configuration file: " + configDir, null, e));
202 } catch (IOException e) {
203 statusManager.add(new ErrorStatus("Error loading logging configuration file: " + configDir, null, e));
204 }
205 }
206
207
208
209
210
211
212
213
214
215 private static SAMLObject performSAML1AttributeResolution(CmdLineParser parser, ApplicationContext appCtx) {
216 BaseSAMLProfileRequestContext requestCtx = buildAttributeRequestContext(parser, appCtx);
217
218 try {
219 Map<String, BaseAttribute> attributes = saml1AA.getAttributes(requestCtx);
220 return saml1AA.buildAttributeStatement(null, attributes.values());
221 } catch (AttributeRequestException e) {
222 errorAndExit("Error encountered during attribute resolution and filtering", e);
223 }
224
225 return null;
226 }
227
228
229
230
231
232
233
234
235
236 private static SAMLObject performSAML2AttributeResolution(CmdLineParser parser, ApplicationContext appCtx) {
237 BaseSAMLProfileRequestContext requestCtx = buildAttributeRequestContext(parser, appCtx);
238
239 try {
240 Map<String, BaseAttribute> attributes = saml2AA.getAttributes(requestCtx);
241 return saml2AA.buildAttributeStatement(null, attributes.values());
242 } catch (AttributeRequestException e) {
243 errorAndExit("Error encountered during attribute resolution and filtering", e);
244 }
245
246 return null;
247 }
248
249
250
251
252
253
254
255
256
257 private static BaseSAMLProfileRequestContext buildAttributeRequestContext(CmdLineParser parser,
258 ApplicationContext appCtx) {
259 String issuer = (String) parser.getOptionValue(CLIParserBuilder.ISSUER_ARG);
260 String requester = (String) parser.getOptionValue(CLIParserBuilder.REQUESTER_ARG);
261
262 RelyingPartyConfiguration rpConfig = new RelyingPartyConfiguration(requester, issuer);
263
264 BaseSAMLProfileRequestContext attribReqCtx = new BaseSAMLProfileRequestContext();
265 attribReqCtx.setInboundMessageIssuer(requester);
266 attribReqCtx.setOutboundMessageIssuer(issuer);
267 attribReqCtx.setLocalEntityId(issuer);
268 attribReqCtx.setRelyingPartyConfiguration(rpConfig);
269
270 String principal = (String) parser.getOptionValue(CLIParserBuilder.PRINCIPAL_ARG);
271 attribReqCtx.setPrincipalName(principal);
272
273 String authnMethod = (String) parser.getOptionValue(CLIParserBuilder.AUTHN_METHOD_ARG);
274 attribReqCtx.setPrincipalAuthenticationMethod(authnMethod);
275
276 return attribReqCtx;
277 }
278
279
280
281
282
283
284 private static void printAttributeStatement(SAMLObject attributeStatement) {
285 if (attributeStatement == null) {
286 System.out.println("No attribute statement.");
287 return;
288 }
289
290 Marshaller statementMarshaller = Configuration.getMarshallerFactory().getMarshaller(attributeStatement);
291
292 try {
293 Element statement = statementMarshaller.marshall(attributeStatement);
294 System.out.println();
295 System.out.println(XMLHelper.prettyPrintXML(statement));
296 } catch (MarshallingException e) {
297 errorAndExit("Unable to marshall attribute statement", e);
298 }
299 }
300
301
302
303
304
305
306 private static void printHelp(PrintStream out) {
307 out.println("Attribute Authority, Command Line Interface");
308 out.println(" This tools provides a command line interface to the Shibboleth Attribute Authority,");
309 out.println(" providing deployers a means to test their attribute resolution and configurations.");
310 out.println();
311 out.println("usage:");
312 out.println(" On Unix systems: ./aacli.sh <PARAMETERS>");
313 out.println(" On Windows systems: .\\aacli.bat <PARAMETERS>");
314 out.println();
315 out.println("Required Parameters:");
316 out.println(String.format(" --%-16s %s", CLIParserBuilder.CONFIG_DIR,
317 "Directory containing attribute authority configuration files"));
318 out.println(String.format(" --%-16s %s", CLIParserBuilder.PRINCIPAL,
319 "Principal name (user id) of the person whose attributes will be retrieved"));
320
321 out.println();
322
323 out.println("Optional Parameters:");
324 out.println(String.format(" --%-16s %s", CLIParserBuilder.HELP, "Print this message"));
325 out.println(String.format(" --%-16s %s", CLIParserBuilder.REQUESTER,
326 "SAML entity ID of the relying party requesting the attributes. For example, the SPs entity ID"));
327 out.println(String.format(" --%-16s %s", CLIParserBuilder.ISSUER,
328 "SAML entity ID of the attribute issuer. For example, the IdPs entity ID"));
329 out.println(String
330 .format(" --%-16s %s", CLIParserBuilder.AUTHN_METHOD, "Method used to authenticate the user"));
331 out
332 .println(String
333 .format(" --%-16s %s", CLIParserBuilder.SAML1,
334 "No-value parameter indicating the attribute authority should answer as if it received a SAML 1 request"));
335
336 out.println();
337 }
338
339
340
341
342
343
344
345 private static void errorAndExit(String errorMessage, Exception e) {
346 if (e == null) {
347 log.error(errorMessage);
348 } else {
349 log.error(errorMessage, e);
350 }
351
352 System.out.flush();
353 System.exit(1);
354 }
355
356
357
358
359 private static class CLIParserBuilder {
360
361
362 public static final String HELP = "help";
363
364 public static final String CONFIG_DIR = "configDir";
365
366 public static final String REQUESTER = "requester";
367
368 public static final String ISSUER = "issuer";
369
370 public static final String PRINCIPAL = "principal";
371
372 public static final String AUTHN_METHOD = "authnMethod";
373
374 public static final String SAML1 = "saml1";
375
376
377 public static CmdLineParser.Option HELP_ARG;
378
379 public static CmdLineParser.Option CONFIG_DIR_ARG;
380
381 public static CmdLineParser.Option REQUESTER_ARG;
382
383 public static CmdLineParser.Option ISSUER_ARG;
384
385 public static CmdLineParser.Option PRINCIPAL_ARG;
386
387 public static CmdLineParser.Option AUTHN_METHOD_ARG;
388
389 public static CmdLineParser.Option SAML1_ARG;
390
391
392
393
394
395
396 public static CmdLineParser buildParser() {
397 CmdLineParser parser = new CmdLineParser();
398
399 HELP_ARG = parser.addBooleanOption(HELP);
400 CONFIG_DIR_ARG = parser.addStringOption(CONFIG_DIR);
401 REQUESTER_ARG = parser.addStringOption(REQUESTER);
402 ISSUER_ARG = parser.addStringOption(ISSUER);
403 PRINCIPAL_ARG = parser.addStringOption(PRINCIPAL);
404 AUTHN_METHOD_ARG = parser.addStringOption(AUTHN_METHOD);
405 SAML1_ARG = parser.addBooleanOption(SAML1);
406
407 return parser;
408 }
409 }
410 }