1 56 package org.opencrx.security.layer.application; 57 58 import java.util.Set ; 59 60 import org.opencrx.kernel.generic.OpenCrxException; 61 import org.opencrx.kernel.generic.SecurityKeys; 62 import org.openmdx.application.log.AppLog; 63 import org.openmdx.base.exception.ServiceException; 64 import org.openmdx.base.text.conversion.Base64; 65 import org.openmdx.compatibility.base.application.configuration.Configuration; 66 import org.openmdx.compatibility.base.dataprovider.cci.AttributeSelectors; 67 import org.openmdx.compatibility.base.dataprovider.cci.AttributeSpecifier; 68 import org.openmdx.compatibility.base.dataprovider.cci.DataproviderObject; 69 import org.openmdx.compatibility.base.dataprovider.cci.DataproviderObject_1_0; 70 import org.openmdx.compatibility.base.dataprovider.cci.DataproviderOperations; 71 import org.openmdx.compatibility.base.dataprovider.cci.DataproviderReply; 72 import org.openmdx.compatibility.base.dataprovider.cci.DataproviderRequest; 73 import org.openmdx.compatibility.base.dataprovider.cci.RequestCollection; 74 import org.openmdx.compatibility.base.dataprovider.cci.ServiceHeader; 75 import org.openmdx.compatibility.base.dataprovider.cci.SharedConfigurationEntries; 76 import org.openmdx.compatibility.base.dataprovider.cci.SystemAttributes; 77 import org.openmdx.compatibility.base.dataprovider.layer.application.ProvidingUid_1; 78 import org.openmdx.compatibility.base.dataprovider.spi.Layer_1_0; 79 import org.openmdx.compatibility.base.naming.Path; 80 import org.openmdx.compatibility.base.query.FilterOperators; 81 import org.openmdx.compatibility.base.query.FilterProperty; 82 import org.openmdx.compatibility.base.query.Quantors; 83 import org.openmdx.kernel.exception.BasicException; 84 import org.openmdx.kernel.id.UUIDs; 85 import org.openmdx.model1.accessor.basic.cci.Model_1_0; 86 87 103 public class OpenCrxSecurity_1 104 extends ProvidingUid_1 { 105 106 public void activate( 108 short id, 109 Configuration configuration, 110 Layer_1_0 delegation 111 ) throws ServiceException, Exception { 112 super.activate( 113 id, 114 configuration, 115 delegation 116 ); 117 118 if(configuration.values(SharedConfigurationEntries.MODEL).size() > 0) { 120 this.model = (Model_1_0)configuration.values(SharedConfigurationEntries.MODEL).get(0); 121 } 122 else { 123 throw new ServiceException( 124 BasicException.Code.DEFAULT_DOMAIN, 125 BasicException.Code.INVALID_CONFIGURATION, 126 null, 127 "A model must be configured with options 'modelPackage' and 'packageImpl'" 128 ); 129 } 130 131 if(configuration.values("realmIdentity").size() > 0) { 133 this.realmIdentity = new Path((String )configuration.values("realmIdentity").get(0)); 134 } 135 else { 136 throw new ServiceException( 137 BasicException.Code.DEFAULT_DOMAIN, 138 BasicException.Code.INVALID_CONFIGURATION, 139 null, 140 "A realm identity must be configured with option 'realmIdentity'" 141 ); 142 } 143 144 } 145 146 private String getPrincipalName( 148 ServiceHeader header 149 ) throws ServiceException { 150 if(header.getPrincipalChain().size() == 0) { 151 return null; 152 } 153 return (String )header.getPrincipalChain().get(0); 154 } 155 156 private DataproviderObject_1_0 getPrincipal( 158 ServiceHeader header, 159 DataproviderRequest request 160 ) throws ServiceException { 161 String principalName = header.getPrincipalChain().size() == 0 162 ? null 163 : (String )header.getPrincipalChain().get(0); 164 String realmName = SecurityKeys.ROOT_PRINCIPAL.equals(principalName) 165 ? "Root" 166 : request.path().get(4); 167 DataproviderObject_1_0 principal = null; 168 try { 169 principal = this.delegation.addGetRequest( 170 this.realmIdentity.getParent().getDescendant(new String []{realmName, "principal", principalName}), 171 AttributeSelectors.ALL_ATTRIBUTES, 172 new AttributeSpecifier[]{} 173 ); 174 } 175 catch(ServiceException e) { 176 AppLog.warning("principal not found in realm", new Object []{principalName, realmName}); 177 e.log(); 178 } 179 return principal; 180 } 181 182 protected void completeObject( 184 ServiceHeader header, 185 Set fetchSet, 186 DataproviderObject_1_0 object 187 ) throws ServiceException { 188 } 189 190 private DataproviderReply completeReply( 192 ServiceHeader header, 193 Set fetchSet, 194 DataproviderReply reply 195 ) throws ServiceException { 196 for(int i = 0; i < reply.getObjects().length; i++) { 197 this.completeObject( 198 header, 199 fetchSet, 200 reply.getObjects()[i] 201 ); 202 } 203 return reply; 204 } 205 206 private void setQualifier( 208 DataproviderObject obj 209 ) throws ServiceException { 210 if( 211 this.model.isSubtypeOf(obj, "org:openmdx:security:realm1:Principal") || 212 this.model.isSubtypeOf(obj, "org:openmdx:security:realm1:Realm") || 213 this.model.isSubtypeOf(obj, "org:openmdx:security:realm1:Permission") || 214 this.model.isSubtypeOf(obj, "org:openmdx:security:realm1:Role") || 215 this.model.isSubtypeOf(obj, "org:openmdx:security:realm1:Policy") 216 ) { 217 obj.clearValues("name").add(obj.path().getBase()); 218 } 219 else if( 220 this.model.isSubtypeOf(obj, "org:openmdx:security:realm1:Credential") 221 ) { 222 obj.clearValues("id").add(obj.path().getBase()); 223 } 224 } 225 226 public DataproviderObject_1_0 retrieveObject( 228 Path identity 229 ) throws ServiceException { 230 return this.delegation.addGetRequest( 231 identity, 232 AttributeSelectors.ALL_ATTRIBUTES, 233 new AttributeSpecifier[]{} 234 ); 235 } 236 237 private DataproviderObject createResult( 239 DataproviderRequest request, 240 String structName 241 ) { 242 DataproviderObject result = new DataproviderObject( 243 request.path().getDescendant( 244 new String []{ "reply", UUIDs.getGenerator().next().toString()} 245 ) 246 ); 247 result.clearValues(SystemAttributes.OBJECT_CLASS).add( 248 structName 249 ); 250 return result; 251 } 252 253 public void prolog( 255 ServiceHeader header, 256 DataproviderRequest[] requests 257 ) throws ServiceException { 258 super.prolog( 259 header, 260 requests 261 ); 262 this.delegation = new RequestCollection( 263 header, 264 this.getDelegation() 265 ); 266 } 267 268 public void epilog( 270 ServiceHeader header, 271 DataproviderRequest[] requests, 272 DataproviderReply[] replies 273 ) throws ServiceException { 274 super.epilog( 275 header, 276 requests, 277 replies 278 ); 279 } 280 281 private void setAttributes( 283 ServiceHeader header, 284 DataproviderObject obj, 285 DataproviderObject_1_0 oldValues 286 ) throws ServiceException { 287 this.setQualifier( 288 obj 289 ); 290 } 291 292 private void changePassword( 294 DataproviderObject_1_0 passwordCredential, 295 DataproviderObject_1_0 changePasswordParams 296 ) throws ServiceException { 297 String oldPassword = changePasswordParams.getValues("oldPassword") != null 298 ? Base64.encode((byte[])changePasswordParams.values("oldPassword").get(0)) 299 : null; 300 if((oldPassword != null) && !oldPassword.equals(passwordCredential.values("password").get(0))) { 301 throw new ServiceException( 302 BasicException.Code.DEFAULT_DOMAIN, 303 BasicException.Code.ASSERTION_FAILURE, 304 new BasicException.Parameter[]{ 305 new BasicException.Parameter("credential", passwordCredential) 306 }, 307 "old password verification mismatch" 308 ); 309 } 310 DataproviderObject changedPasswordCredential = new DataproviderObject( 311 passwordCredential 312 ); 313 changedPasswordCredential.clearValues("password").add( 314 Base64.encode((byte[])changePasswordParams.values("password").get(0)) 315 ); 316 this.delegation.addReplaceRequest( 317 changedPasswordCredential 318 ); 319 } 320 321 private void checkPermission( 323 ServiceHeader header, 324 DataproviderRequest request 325 ) throws ServiceException { 326 327 short operation = request.operation(); 328 329 if( 331 operation == DataproviderOperations.OBJECT_RETRIEVAL || 332 operation == DataproviderOperations.ITERATION_START || 333 operation == DataproviderOperations.ITERATION_CONTINUATION 334 ) { 335 return; 336 } 337 String principalName = this.getPrincipalName(header); 340 if(( 341 !principalName.startsWith("admin" + SecurityKeys.ID_SEPARATOR) && 342 !(request.path().getParent().isLike(PATH_PATTERN_PRINCIPALS) && request.path().getBase().equals(principalName))) 343 ) { 344 throw new ServiceException( 345 OpenCrxException.DOMAIN, 346 OpenCrxException.AUTHORIZATION_FAILURE_UPDATE, 347 new BasicException.Parameter[]{ 348 new BasicException.Parameter("object", request.path()), 349 new BasicException.Parameter("param0", request.path()) 350 }, 351 "No permission for " + DataproviderOperations.toString(operation) + " on object" 352 ); 353 } 354 } 355 356 360 private void updateRealm( 361 ServiceHeader header, 362 DataproviderRequest request 363 ) throws ServiceException { 364 if( 365 (request.path().size() >= PATH_PATTERN_REALM_COMPOSITE.size()) && 366 request.path().getPrefix(PATH_PATTERN_REALM_COMPOSITE.size()).isLike(PATH_PATTERN_REALM_COMPOSITE) 367 ) { 368 try { 369 Path realmIdentity = request.path().getPrefix(7); 370 RequestCollection delegation = new RequestCollection( 371 header, 372 this.getDelegation() 373 ); 374 DataproviderObject_1_0 realm = delegation.addGetRequest( 375 realmIdentity, 376 AttributeSelectors.SPECIFIED_AND_SYSTEM_ATTRIBUTES, 377 new AttributeSpecifier[]{} 378 ); 379 delegation.addReplaceRequest( 380 new DataproviderObject(realm) 381 ); 382 } 383 catch(ServiceException e) { 384 if(BasicException.Code.NOT_FOUND != e.getExceptionCode()) { 386 throw e; 387 } 388 } 389 } 390 } 391 392 public DataproviderReply remove( 394 ServiceHeader header, 395 DataproviderRequest request 396 ) throws ServiceException { 397 this.checkPermission( 398 header, 399 request 400 ); 401 this.updateRealm( 402 header, 403 request 404 ); 405 return super.remove( 406 header, 407 request 408 ); 409 } 410 411 public DataproviderReply create( 413 ServiceHeader header, 414 DataproviderRequest request 415 ) throws ServiceException { 416 this.checkPermission( 417 header, 418 request 419 ); 420 this.setAttributes( 421 header, 422 request.object(), 423 null 424 ); 425 this.updateRealm( 426 header, 427 request 428 ); 429 return super.create(header, request); 430 } 431 432 public DataproviderReply replace( 434 ServiceHeader header, 435 DataproviderRequest request 436 ) throws ServiceException { 437 this.checkPermission( 438 header, 439 request 440 ); 441 this.setAttributes( 442 header, 443 request.object(), 444 this.delegation.addGetRequest( 445 request.path(), 446 AttributeSelectors.ALL_ATTRIBUTES, 447 null 448 ) 449 ); 450 this.updateRealm( 451 header, 452 request 453 ); 454 try { 455 return super.replace( 456 header, 457 request 458 ); 459 } 460 catch(ServiceException e) { 461 if( 464 (e.getExceptionCode() == BasicException.Code.CONCURRENT_ACCESS_FAILURE) && 465 request.path().isLike(PATH_PATTERN_REALM) 466 ) { 467 return new DataproviderReply(request.object()); 468 } 469 throw e; 470 } 471 } 472 473 public DataproviderReply get( 475 ServiceHeader header, 476 DataproviderRequest request 477 ) throws ServiceException { 478 this.checkPermission( 479 header, 480 request 481 ); 482 return this.completeReply( 483 header, 484 request.attributeSelector() == AttributeSelectors.SPECIFIED_AND_TYPICAL_ATTRIBUTES 485 ? request.attributeSpecifierAsMap().keySet() 486 : null, 487 super.get( 488 header, 489 request 490 ) 491 ); 492 } 493 494 public DataproviderReply operation( 496 ServiceHeader header, 497 DataproviderRequest request 498 ) throws ServiceException { 499 this.checkPermission( 500 header, 501 request 502 ); 503 504 String operationName = request.path().get( 505 request.path().size() - 2 506 ); 507 DataproviderObject_1_0 source = this.retrieveObject( 508 request.path().getPrefix(request.path().size() - 2) 509 ); 510 String sourceClass = (String )source.values(SystemAttributes.OBJECT_CLASS).get(0); 511 DataproviderObject param = request.object(); 512 513 DataproviderObject reply = null; 515 if("org:openmdx:security:authentication1:Password".equals(sourceClass)) { 516 if("change".equals(operationName)) { 517 this.changePassword( 518 source, 519 param 520 ); 521 reply = this.createResult( 522 request, 523 "org:openmdx:base:Void" 524 ); 525 } 526 } 527 528 if(reply != null) { 530 return new DataproviderReply( 531 reply 532 ); 533 } 534 else { 535 return super.operation( 536 header, 537 request 538 ); 539 } 540 } 541 542 public DataproviderReply find( 544 ServiceHeader header, 545 DataproviderRequest request 546 ) throws ServiceException { 547 this.checkPermission( 548 header, 549 request 550 ); 551 String principalName = this.getPrincipalName(header); 552 String realmName = principalName.startsWith("admin" + SecurityKeys.ID_SEPARATOR) 553 ? principalName.substring(principalName.indexOf("-") + 1) 554 : ""; 555 if(request.path().isLike(PATH_PATTERN_PRINCIPALS)) { 557 boolean containsSubjectFilter = false; 558 for(int i = 0; i < request.attributeFilter().length; i++) { 559 if("subject".equals(request.attributeFilter()[i].name())) { 560 containsSubjectFilter = true; 561 break; 562 } 563 } 564 if( 567 !containsSubjectFilter && 568 !"Root".equals(realmName) && 569 !realmName.equals(request.path().get(request.path().size()-2)) 570 ) { 571 request.addAttributeFilterProperty( 572 new FilterProperty( 573 Quantors.THERE_EXISTS, 574 SystemAttributes.OBJECT_CLASS, 575 FilterOperators.IS_IN, 576 new String []{ 577 "org:opencrx:security:realm1:PrincipalGroup" 578 } 579 ) 580 ); 581 } 582 } 583 else if(request.path().isLike(PATH_PATTERN_SUBJECTS)) { 585 if(!"Root".equals(realmName)) { 587 request.addAttributeFilterProperty( 588 new FilterProperty( 589 Quantors.FOR_ALL, 590 SystemAttributes.OBJECT_CLASS, 591 FilterOperators.IS_IN, 592 new String []{} 593 ) 594 ); 595 } 596 } 597 return this.completeReply( 598 header, 599 request.attributeSelector() == AttributeSelectors.SPECIFIED_AND_TYPICAL_ATTRIBUTES 600 ? request.attributeSpecifierAsMap().keySet() 601 : null, 602 super.find( 603 header, 604 request 605 ) 606 ); 607 } 608 609 private static final Path PATH_PATTERN_PRINCIPALS = 613 new Path("xri:@openmdx:org.openmdx.security.realm1/provider/:*/segment/:*/realm/:*/principal"); 614 private static final Path PATH_PATTERN_REALM = 615 new Path("xri:@openmdx:org.openmdx.security.realm1/provider/:*/segment/:*/realm/:*"); 616 private static final Path PATH_PATTERN_REALM_COMPOSITE = 617 new Path("xri:@openmdx:org.openmdx.security.realm1/provider/:*/segment/:*/realm/:*/:*"); 618 private static final Path PATH_PATTERN_SUBJECTS = 619 new Path("xri:@openmdx:org.opencrx.security.identity1/provider/:*/segment/:*/subject"); 620 621 private RequestCollection delegation = null; 622 private Path realmIdentity = null; 623 private Model_1_0 model = null; 624 625 } 626 627 | Popular Tags |