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