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