1 package org.opensciencegrid.authz.client;
2
3 /***
4 *
5 * A base class that abstracts most of the SAML communication
6 * on the client side
7 *
8
9 Author: Markus Lorch
10 Project: OpenScienceGrid Privilege
11 Date: 2004-11-16
12
13 changes: 2005-01-04 modified interfaces to opensaml to use stock opensaml 1.0.1
14 added use of separate osg-opensaml-extension package
15
16 2005-01-07 moved away from Globus depenencies to new WS client stub
17 2005-01-08 code cleanup, moved general SAML utility functions to saml.SAMLUtil
18 2005-01-18 added system properties for subject.dn and subject.fqan (for testing)
19
20
21 status: this implementation currently lacks
22 - extraction of subject DN from gss context
23 - extraction of FQAN from the gss context (attribute certificate support)
24 --> these parameters are gotten via the system properties subject.dn and subject.fqan
25 --> subject.fqan is optional
26 */
27
28 import java.net.URL;
29 import java.util.Set;
30 import java.util.Iterator;
31 import java.util.StringTokenizer;
32 import java.util.ArrayList;
33 import java.util.Date;
34 import java.security.cert.X509Certificate;
35 import javax.security.auth.Subject;
36
37 import org.apache.axis.message.MessageElement;
38 import org.apache.log4j.Category;
39 import org.globus.axis.gsi.GSIConstants;
40 import org.globus.gsi.gssapi.GlobusGSSCredentialImpl;
41 import org.globus.wsrf.impl.security.authorization.NoAuthorization;
42 import org.globus.wsrf.security.Constants;
43
44 import org.w3c.dom.Element;
45
46 import org.opensaml.v1_0_1.XML;
47 import org.opensaml.v1_0_1.QName;
48 import org.opensaml.v1_0_1.SAMLAction;
49 import org.opensaml.v1_0_1.SAMLDecision;
50 import org.opensaml.v1_0_1.SAMLRequest;
51 import org.opensaml.v1_0_1.SAMLSubject;
52 import org.opensaml.v1_0_1.SAMLNameIdentifier;
53 import org.opensaml.v1_0_1.SAMLResponse;
54 import org.opensaml.v1_0_1.SAMLException;
55 import org.opensaml.v1_0_1.SAMLAssertion;
56 import org.opensaml.v1_0_1.SAMLAttribute;
57 import org.opensaml.v1_0_1.SAMLAttributeStatement;
58 import org.opensaml.v1_0_1.SAMLAttributeDesignator;
59 import org.opensaml.v1_0_1.SAMLAuthorizationDecisionQuery;
60 import org.opensaml.v1_0_1.SAMLAuthorizationDecisionStatement;
61
62 import org.opensciencegrid.authz.saml.ObligatedAuthorizationDecisionStatement;
63 import org.opensciencegrid.authz.saml.XACMLObligation;
64 import org.opensciencegrid.authz.saml.SAMLExtensionInit;
65 import org.opensciencegrid.authz.saml.SAMLUtil;
66
67 import org.opensciencegrid.authz.common.LocalId;
68 import org.opensciencegrid.authz.common.OSGAuthorizationConstants;
69
70 import org.opensciencegrid.authz.stubs.AuthorizationServiceLocator;
71 import org.opensciencegrid.authz.stubs.SAMLRequestPortType;
72 import org.opensciencegrid.authz.stubs.SAMLRequestType;
73 import org.opensciencegrid.authz.stubs.SAMLResponseType;
74
75
76
77
78 public class SAMLAuthZClientBase {
79
80
81 /*** logging category */
82 static Category log = Category.getInstance(SAMLAuthZClientBase.class.getName());
83
84
85
86 /***
87 * create a SAML Subject from a DN String
88 *
89 */
90
91 public SAMLSubject getSAMLSubjectFromString (String subjectName) throws SAMLException {
92
93 if(subjectName==null || subjectName.length()==0) {
94 log.error("The subjectName parameter must not be null or empty");
95 return null;
96 }
97
98 log.debug("Creating SAMLSubject from string: "+subjectName);
99
100 String nameQualifier = "";
101 String format = "urn:osasis:names:tc:SAML:1.1nameid-format:X509SubjectName";
102 ArrayList confirmationMethods = null;
103
104 SAMLNameIdentifier subjectId = new SAMLNameIdentifier(subjectName,
105 nameQualifier,
106 format);
107
108 confirmationMethods = new ArrayList(1);
109 confirmationMethods.add("urn:oasis:names:tc:SAML:1.0:am:X509-PKI");
110
111 log.debug("Subject name " + subjectName + " nameQualifier "
112 + nameQualifier + " format " + format);
113
114
115 return new SAMLSubject(subjectId, confirmationMethods, null, null);
116
117 }
118
119
120 /***
121 * create a SAML Subject based on the subject name of the
122 * peer certificate (issuing EEC in case of proxy) subjecta
123 * as extracted from the supplied gss context
124 * (not implemented)
125 * Subject will include certificate path as confirmation method
126 * (not implemented)
127 */
128
129 public SAMLSubject getSAMLSubjectFromGSS (String gssContext) throws SAMLException {
130
131
132 String subjectName="/CN=Dummy Subject";
133
134 log.debug("Creating SAMLSubject from peer certificate subject (not implemented): "+subjectName);
135
136 String nameQualifier = "";
137 String format = "urn:osasis:names:tc:SAML:1.1nameid-format:X509SubjectName";
138 ArrayList confirmationMethods = null;
139
140 SAMLNameIdentifier subjectId = new SAMLNameIdentifier(subjectName,
141 nameQualifier,
142 format);
143
144
145
146
147
148
149
150 confirmationMethods = new ArrayList(1);
151 confirmationMethods.add("urn:oasis:names:tc:SAML:1.0:am:X509-PKI");
152
153 log.debug("Subject name " + subjectName + " nameQualifier "
154 + nameQualifier + " format " + format);
155
156
157 return new SAMLSubject(subjectId, confirmationMethods, null, null);
158
159 }
160
161
162 /***
163 * Create an ArrayList with the OSG Mapping Action as the single SAMLAction element
164 */
165
166 public ArrayList createMappingActions() throws SAMLException{
167
168
169 //****** create the SAML Action *************************************************************
170
171 ArrayList samlActions = new ArrayList();
172 SAMLAction samlAction =
173 new SAMLAction(OSGAuthorizationConstants.AUTHZ_NS, OSGAuthorizationConstants.ACCESS_AS_LOCAL_ID);
174 samlActions.add(samlAction);
175
176 return samlActions;
177
178 }
179
180
181
182 /**
183 * Create an ArrayList with a single SAML Attribute Statement (bound to the
184 * specified SAMLSubject) embedded in a single SAML Assertion. The attribute
185 * statement will hold a single VOMS FQAN attribute.
186 */
187
188 public ArrayList createFQANEvidenceFromString(SAMLSubject samlSubject, String fqanIssuer, String fqan)
189 throws SAMLException, CloneNotSupportedException {
190
191 ArrayList samlEvidence = null;
192
193
194 if(fqan!=null && fqan.length()!=0 && fqanIssuer!=null && fqanIssuer.length()!=0) {
195
196 samlEvidence = new ArrayList(1);
197
198 log.debug("Creating Evidence based on FQAN String: "+fqan+ " from: "+fqanIssuer);
199
200 ArrayList values = new ArrayList(1);
201 values.add(fqan);
202
203 SAMLAttribute sa = new SAMLAttribute("FQAN", OSGAuthorizationConstants.AUTHZ_NS, null,0, values);
204
205 ArrayList attrs = new ArrayList(1);
206 attrs.add(sa);
207
208 SAMLAttributeStatement stmt = new SAMLAttributeStatement((SAMLSubject)samlSubject.clone(), attrs);
209
210 ArrayList stmts = new ArrayList(1);
211 stmts.add(stmt);
212
213 SAMLAssertion ass = new SAMLAssertion(fqanIssuer, new Date(), new Date(), null, null, stmts);
214
215 samlEvidence.add(ass);
216 }
217 else {
218 log.warn("fqan and fqanIssuer information must be provided for evidence element to be created");
219 }
220
221
222 return samlEvidence;
223
224 }
225
226
227 /***
228 * Create an ArrayList with a single SAML Attribute Statement embedded in
229 * a single SAML Assertion. The attribute statement will hold a single
230 * VOMS FQAN attribute. The FQAN information is extracted from the GSS
231 * context.
232 * (NOT IMPLEMENTED)
233 */
234
235 public ArrayList createFQANEvidenceFromGSS(String gssContext) {
236
237 return null;
238
239 }
240
241
242
243
244 /*** General method to create and submit a SAMLAuthorizationDecisionQuery
245 * to an Authorization Service. Supports standard as well as
246 * obligated authorization decision responses.
247 * Will return the AuthorizationDecisionStatement object
248 * (may be SAMLAuthorizationDecisionStatement or ObligatedAuthorizationDecisionStatement)
249 * returns null or throws exception if no valid response/statement
250 * was received
251 */
252
253 public SAMLAuthorizationDecisionStatement queryAuthZService( SAMLSubject samlSubject,
254 ArrayList samlEvidence,
255 ArrayList samlActions,
256 String requestedServiceName,
257 URL contact)
258 throws SAMLException, javax.xml.rpc.ServiceException, java.rmi.RemoteException, java.lang.Exception {
259
260
261 //****** initialize SAML Extensions *****
262 SAMLExtensionInit.init();
263
264
265 //***** assemble the SAML Authorization Decision Query*******
266
267 SAMLAuthorizationDecisionQuery query = null;
268
269 query = new SAMLAuthorizationDecisionQuery(samlSubject,
270 requestedServiceName,
271 samlActions, samlEvidence);
272
273
274 //**** set valid respondWiths ***
275 // these define with what type of statement the identity mapping service may respond to
276 // our service request
277
278 ArrayList supportedRespondWiths = new ArrayList();
279 supportedRespondWiths.add( OSGAuthorizationConstants.AUTHZDECISIONSTATEMENT);
280 supportedRespondWiths.add( OSGAuthorizationConstants.OBLIGATEDAUTHZDECISIONSTATEMENT);
281
282 //***** assemble the SAML Request that holds the query and submit request***
283 SAMLRequest request = null;
284 request = new SAMLRequest(supportedRespondWiths, query, null, null);
285
286 SAMLRequestType requestType = new SAMLRequestType();
287 requestType.set_any(new MessageElement[] {
288 new MessageElement((Element)request.toDOM())});
289
290 SAMLResponseType responseType = null;
291
292 AuthorizationServiceLocator serviceLocator = new AuthorizationServiceLocator();
293
294 log.debug("Sending SAML query/request to "+contact+" "+request);
295
296 //***** Send Request
297 SAMLRequestPortType authzPort =
298 (SAMLRequestPortType) serviceLocator.getAuthorizationServicePort(contact);
299 if (getGlobusCredentials() != null) {
300 org.apache.axis.client.Stub stub = (org.apache.axis.client.Stub) authzPort;
301 log.debug("Setting Globus GSI credentials on the WS stub.");
302 stub._setProperty(Constants.GSI_TRANSPORT, Constants.ENCRYPTION);
303 stub._setProperty(GSIConstants.GSI_CREDENTIALS, getGlobusCredentials());
304 stub._setProperty(Constants.AUTHORIZATION, NoAuthorization.getInstance());
305 }
306
307 responseType = authzPort.SAMLRequest(requestType);
308
309
310 //****** Process Response *********************************************************************
311
312 if (responseType == null) {
313 // This is an error since atleast a response with no actions
314 // should have been returned.
315 log.error("Authorization query/request failed - received a null response");
316 return null;
317 }
318
319 SAMLResponse response = null;
320 MessageElement[] msgElement = responseType.get_any();
321 response = new SAMLResponse(msgElement[0].getAsDOM());
322
323 // Assert that our request id matches the InResponseTo
324 String inResponseTo = response.getInResponseTo();
325 if ((inResponseTo == null) ||
326 (!inResponseTo.equals(request.getId()))) {
327 log.error("Authorization query/request failed - received a bad inResponseTo");
328 return null;
329 }
330
331 log.debug("Received response corresponding to our request: "+response);
332
333
334 //****** Extract Authorization Decision Statements from Response ******************************
335
336 log.debug("Extracting authorization decision statements from response");
337
338 // response must have assertions
339 Iterator assertionsItr = response.getAssertions();
340 if ((assertionsItr != null) && (assertionsItr.hasNext())) {
341 SAMLAssertion assertion = (SAMLAssertion)assertionsItr.next();
342
343 // assertions must hold statements
344 Iterator stmtsItr = assertion.getStatements();
345 if ((stmtsItr != null) && (stmtsItr.hasNext())) {
346 Object obj = stmtsItr.next();
347
348 // accepted statements include AuthorizationDecisionStatements
349 // and ObligatedAuthorizationDecisionStatements
350 if ((obj instanceof ObligatedAuthorizationDecisionStatement)
351 || (obj instanceof SAMLAuthorizationDecisionStatement) ) {
352 log.debug("Received and returning Authorization Decision Statement");
353 return (SAMLAuthorizationDecisionStatement) obj;
354 }
355 else {
356 log.error("Received statement was not an Authorization Decison Statement");
357 }
358 }
359 else {
360 log.error("Received assertion did not contain a SAML statement");
361 }
362
363 } // end if assertionsItr
364 else {
365 log.error("Received response did not contain a SAML Assertion");
366 }
367
368 return null;
369
370 } // end queryAuthZService
371
372
373
374 /** Specialized method to process a received authorization decision statement
375 * that holds obligations with local user ID qualifications
376 *
377 * If the response is valid, action permitted and obligations present then it
378 * will return a LocaldQualifiers object with the attributes conveyed
379 * via obligations set. If the decision is permit but no obligations present
380 * or only partially present then the LocalID object will have
381 * some elements be null. If decision is deny, unknown obligations present
382 * or the response is invalid for other reasons then the "null" will be returned.
383 *
384 * Supported obligations are:
385 * - UserIdObligation with single local user name as string attribute
386 * - GroupIdObligation with single local primary group name as string attribute
387 * - SupplementalGroupIdsObligation with space delimited list of local group names as string attribute
388 * - RootPathObligation with root path as string attribute
389 * - RelativeHomePathObligation with home path relative to root path as string attribute
390 */
391
392 public LocalId processAuthzStmt(SAMLAuthorizationDecisionStatement stmt, String resource,
393 ArrayList actions, SAMLSubject samlSubject) {
394
395 log.debug("Processing Authorization Decision Statement");
396
397 LocalId localIds = null;
398
399
400 if ((stmt.getDecision().equals(SAMLDecision.PERMIT)) &&
401 (stmt.getResource().equals(resource))) {
402 log.debug("Authorization decision is Permit and Resource matches");
403
404 if (SAMLUtil.samlSubjectMatch(samlSubject, stmt.getSubject())) {
405 log.debug("Response subject name matches request subject name");
406
407 Iterator actionIterator = stmt.getActions();
408
409 ArrayList decisionActions = new ArrayList();
410 while (actionIterator.hasNext()) {
411 SAMLAction a = (SAMLAction) actionIterator.next();
412 decisionActions.add(a);
413 log.debug("Authorized action: " + a.getNamespace() + " " +a.getData());
414 }
415 int found=0;
416 for(int j=0; j<actions.size(); j++) {
417 found = 0;
418
419
420 for(int i=0;i<decisionActions.size();i++){
421 if(SAMLUtil.samlActionMatch((SAMLAction)actions.get(j),
422 (SAMLAction)decisionActions.get(i))) {
423 found = 1;
424 break;
425 }
426 }
427 if(found!=1) break;
428 }
429
430 if(found==1) {
431
432
433 log.debug("Authorized actions include requested actions");
434 localIds = new LocalId();
435 } else {
436 log.warn("Authorization decision did not permit requested actions");
437 }
438
439 } else {
440 log.warn("Response subject does not match request subject");
441 log.debug("Request Subject Name "+samlSubject.getName().getName());
442 log.debug("Response Subject Name "+stmt.getSubject().getName().getName());
443 if(samlSubject.getName().getName().equals(stmt.getSubject().getName().getName()))
444 log.debug("The name is equal");
445 else
446 log.debug("The name is not equal");
447 log.debug("Request Subject Name Qualifier "+samlSubject.getName().getNameQualifier());
448 log.debug("Response Subject Name Qualifier "+stmt.getSubject().getName().getNameQualifier());
449 if(samlSubject.getName().getNameQualifier().equals(stmt.getSubject().getName().getNameQualifier()))
450 log.debug("The name qualifier is equal");
451 else
452 log.debug("The name qualifier is not equal");
453 log.debug("Request Subject Name Format "+samlSubject.getName().getFormat());
454 log.debug("Response Subject Name Format "+stmt.getSubject().getName().getFormat());
455 if(samlSubject.getName().getFormat().equals(stmt.getSubject().getName().getFormat()))
456 log.debug("The format is equal");
457 else
458 log.debug("The format is not equal");
459 }
460 }
461
462
463 if(localIds == null) {
464 return null;
465 }
466
467
468
469 if(stmt instanceof ObligatedAuthorizationDecisionStatement) {
470 log.debug("Processing Obligations");
471
472 Iterator obligationIterator = ((ObligatedAuthorizationDecisionStatement) stmt).getXACMLObligations();
473
474 while (obligationIterator.hasNext()) {
475 XACMLObligation o = (XACMLObligation) obligationIterator.next();
476
477 if(o.getObligationId().equals(OSGAuthorizationConstants.USERIDOBLIGATION)
478 && o.getFullfillOn().equals("Permit") ) {
479 log.debug("Found UserId obligation");
480 if(localIds.getUserName() !=null)
481 log.warn("Warning multiple UserId Obligations, overriding previous");
482 if(o.getAttributeId().equals(OSGAuthorizationConstants.USERIDATTRIBUTE)
483 && o.getDatatype().equals(OSGAuthorizationConstants.STRINGDATATYPE) ) {
484 localIds.setUserName(o.getValue());
485 } else {
486 log.error("Obligation has unexpected attributeId or datatype");
487 return null;
488 }
489 }
490 else if(o.getObligationId().equals(OSGAuthorizationConstants.GROUPIDOBLIGATION)
491 && o.getFullfillOn().equals("Permit") ) {
492 log.debug("Found GroupId obligation");
493 if(localIds.getGroupName() !=null)
494 log.warn("Warning multiple GroupId Obligations, overriding previous");
495 if(o.getAttributeId().equals(OSGAuthorizationConstants.GROUPIDATTRIBUTE)
496 && o.getDatatype().equals(OSGAuthorizationConstants.STRINGDATATYPE) ) {
497 localIds.setGroupName(o.getValue());
498 } else {
499 log.error("Obligation has unexpected attributeId or datatype");
500 return null;
501 }
502 }
503 else if(o.getObligationId().equals(OSGAuthorizationConstants.SUPGROUPIDSOBLIGATION)
504 && o.getFullfillOn().equals("Permit") ) {
505 log.debug("Found SupplementalGroupId obligation");
506 if(localIds.getGroupName() !=null)
507 log.warn("Warning multiple SupplementalGroupIds Obligations, overriding previous");
508 if(o.getAttributeId().equals(OSGAuthorizationConstants.SUPGROUPIDSATTRIBUTE)
509 && o.getDatatype().equals(OSGAuthorizationConstants.STRINGDATATYPE) ) {
510
511 localIds.setSupplementalGroupNames(o.getValue().split("//s"));
512 } else {
513 log.error("Obligation has unexpected attributeId or datatype");
514 return null;
515 }
516 }
517 else if(o.getObligationId().equals(OSGAuthorizationConstants.ROOTPATHOBLIGATION)
518 && o.getFullfillOn().equals("Permit") ) {
519 log.debug("Found Root Path obligation");
520 if(localIds.getGroupName() !=null)
521 log.warn("Warning multiple Root Path Obligations, overriding previous");
522 if(o.getAttributeId().equals(OSGAuthorizationConstants.ROOTPATHATTRIBUTE)
523 && o.getDatatype().equals(OSGAuthorizationConstants.STRINGDATATYPE) ) {
524 localIds.setRootPath(o.getValue());
525 } else {
526 log.error("Obligation has unexpected attributeId or datatype");
527 return null;
528 }
529 }
530 else if(o.getObligationId().equals(OSGAuthorizationConstants.RELHOMEPATHOBLIGATION)
531 && o.getFullfillOn().equals("Permit") ) {
532 log.debug("Found Relative Home Path obligation");
533 if(localIds.getRelativeHomePath() !=null)
534 log.warn("Warning multiple Relative Home Path Obligations, overriding previous");
535 if(o.getAttributeId().equals(OSGAuthorizationConstants.RELHOMEPATHATTRIBUTE)
536 && o.getDatatype().equals(OSGAuthorizationConstants.STRINGDATATYPE) ) {
537 localIds.setRelativeHomePath(o.getValue());
538 } else {
539 log.error("Obligation has unexpected attributeId or datatype");
540 return null;
541 }
542 }
543 else {
544 log.error("Found unsupported obligation - mapping denied");
545 return null;
546 }
547
548
549
550 }
551
552 }
553
554 return localIds;
555
556 }
557
558 /***
559 * Holds value of property globusCredentials.
560 */
561 private GlobusGSSCredentialImpl globusCredentials;
562
563 /***
564 * Getter for property globusCredentials.
565 * @return Value of property globusCredentials.
566 */
567 public GlobusGSSCredentialImpl getGlobusCredentials() {
568 return this.globusCredentials;
569 }
570
571 /***
572 * Setter for property globusCredentials.
573 * @param globusCredentials New value of property globusCredentials.
574 */
575 public void setGlobusCredentials(GlobusGSSCredentialImpl globusCredentials) {
576 this.globusCredentials = globusCredentials;
577 }
578
579 }