diff options
author | Thomas Lenz <tlenz@iaik.tugraz.at> | 2014-02-03 08:06:55 +0100 |
---|---|---|
committer | Thomas Lenz <tlenz@iaik.tugraz.at> | 2014-02-03 08:06:55 +0100 |
commit | 04ba04826a5f88e0459b7a47a55118933d929cc6 (patch) | |
tree | 7f5083300ac4026bbf06b51ac8c3ba01423bbd29 /id/ConfigWebTool/src/main/java/at | |
parent | 080e499cc22a0065ea7f47e04b6c0f336533e21e (diff) | |
parent | 3c1884ee275350e7b2a78256342d9610b1766898 (diff) | |
download | moa-id-spss-04ba04826a5f88e0459b7a47a55118933d929cc6.tar.gz moa-id-spss-04ba04826a5f88e0459b7a47a55118933d929cc6.tar.bz2 moa-id-spss-04ba04826a5f88e0459b7a47a55118933d929cc6.zip |
Merge remote-tracking branch 'remotes/origin/outgoingstork' into moa2_0_tlenz
Conflicts:
id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/data/StorkAttributes.java
id/oa/src/main/java/at/gv/egovernment/moa/id/demoOA/servlet/pvp2/BuildMetadata.java
id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/auth/servlet/GetForeignIDServlet.java
id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/auth/servlet/PEPSConnectorServlet.java
id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/auth/stork/STORKAuthnRequestProcessor.java
id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/auth/validator/parep/client/szrgw/SZRGWClient.java
id/server/idserverlib/src/main/resources/resources/templates/loginFormFull.html
Diffstat (limited to 'id/ConfigWebTool/src/main/java/at')
8 files changed, 395 insertions, 31 deletions
diff --git a/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/data/GeneralStorkConfig.java b/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/data/GeneralStorkConfig.java index f21be44ba..980aa4731 100644 --- a/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/data/GeneralStorkConfig.java +++ b/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/data/GeneralStorkConfig.java @@ -22,6 +22,81 @@ *******************************************************************************/ package at.gv.egovernment.moa.id.configuration.data; +import java.util.ArrayList; +import java.util.List; + +import at.gv.egovernment.moa.id.commons.db.dao.config.AuthComponentGeneral; +import at.gv.egovernment.moa.id.commons.db.dao.config.CPEPS; +import at.gv.egovernment.moa.id.commons.db.dao.config.ForeignIdentities; +import at.gv.egovernment.moa.id.commons.db.dao.config.MOAIDConfiguration; +import at.gv.egovernment.moa.id.commons.db.dao.config.STORK; +import at.gv.egovernment.moa.id.commons.db.dao.config.StorkAttribute; + public class GeneralStorkConfig { + private List<CPEPS> cpepslist; + private List<StorkAttribute> attributes; + private int qaa; + + public void parse(MOAIDConfiguration config) { + + if (config != null) { + AuthComponentGeneral auth = config.getAuthComponentGeneral(); + + if (auth != null) { + ForeignIdentities foreign = auth.getForeignIdentities(); + + if (foreign != null) { + STORK stork = foreign.getSTORK(); + if (stork != null) { + // deep clone all the things + // to foreclose lazyloading session timeouts + cpepslist = new ArrayList<CPEPS>(); + for(CPEPS current : stork.getCPEPS()) { + cpepslist.add(current); + } + + List<StorkAttribute> tmp = stork.getAttributes(); + if(null != tmp) { + attributes = new ArrayList<StorkAttribute>(); + for(StorkAttribute current : tmp) + attributes.add(current); + } + if(attributes.isEmpty()) + attributes.add(new StorkAttribute()); + + try { + qaa = stork.getQualityAuthenticationAssuranceLevel(); + } catch(NullPointerException e) { + qaa = 4; + } + } + } + } + } + } + + public List<CPEPS> getCpepslist() { + return cpepslist; + } + + public void setCpepslist(List<CPEPS> list) { + cpepslist = list; + } + + public List<StorkAttribute> getAttributes() { + return attributes; + } + + public void setAttributes(List<StorkAttribute> attributes) { + this.attributes = attributes; + } + + public int getDefaultQaa() { + return qaa; + } + + public void setDefaultQaa(int qaa) { + this.qaa = qaa; + } } diff --git a/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/data/oa/AttributeHelper.java b/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/data/oa/AttributeHelper.java new file mode 100644 index 000000000..349f3bf4a --- /dev/null +++ b/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/data/oa/AttributeHelper.java @@ -0,0 +1,60 @@ +package at.gv.egovernment.moa.id.configuration.data.oa; + +import at.gv.egovernment.moa.id.commons.db.dao.config.OAStorkAttribute; +import at.gv.egovernment.moa.id.commons.db.dao.config.StorkAttribute; + +public class AttributeHelper { + private boolean isUsed = false; + private String name; + private boolean mandatory; + private boolean readonly; + + public AttributeHelper() { + // TODO Auto-generated constructor stub + } + + public AttributeHelper(OAStorkAttribute attribute) { + isUsed = true; + name = attribute.getName(); + mandatory = attribute.isMandatory(); + } + + public AttributeHelper(StorkAttribute attribute) { + name = attribute.getName(); + mandatory = false; + readonly = attribute.isMandatory(); + isUsed = readonly; + } + + public boolean isUsed() { + return isUsed; + } + + public void setUsed(boolean used) { + isUsed = used; + } + + public String getName() { + return name; + } + + public void setName(String newname) { + name = newname; + } + + public boolean isMandatory() { + return mandatory; + } + + public void setMandatory(boolean value) { + mandatory = value; + } + + public boolean isReadOnly() { + return readonly; + } + + public void setReadOnly(boolean value) { + // we do not allow setting the readonly field + } +}
\ No newline at end of file diff --git a/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/data/oa/OASTORKConfig.java b/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/data/oa/OASTORKConfig.java index d7c71105d..da07b10b0 100644 --- a/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/data/oa/OASTORKConfig.java +++ b/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/data/oa/OASTORKConfig.java @@ -22,6 +22,108 @@ *******************************************************************************/ package at.gv.egovernment.moa.id.configuration.data.oa; +import java.util.ArrayList; +import java.util.List; + +import at.gv.egovernment.moa.id.commons.db.ConfigurationDBRead; +import at.gv.egovernment.moa.id.commons.db.dao.config.AuthComponentOA; +import at.gv.egovernment.moa.id.commons.db.dao.config.OASTORK; +import at.gv.egovernment.moa.id.commons.db.dao.config.OAStorkAttribute; +import at.gv.egovernment.moa.id.commons.db.dao.config.OnlineApplication; +import at.gv.egovernment.moa.id.commons.db.dao.config.StorkAttribute; + public class OASTORKConfig { + private boolean isStorkLogonEnabled = false; + private int qaa; + + private List<AttributeHelper> attributes; + + public OASTORKConfig() { + + } + + /** + * Parses the OA config for stork entities. + * + * @param dbOAConfig + * the db oa config + */ + public void parse(OnlineApplication dbOAConfig) { + AuthComponentOA authdata = dbOAConfig.getAuthComponentOA(); + if (authdata != null) { + OASTORK config = authdata.getOASTORK(); + if(config != null) { + setStorkLogonEnabled(config.isStorkLogonEnabled()); + + try { + setQaa(config.getQaa()); + } catch(NullPointerException e) { + // if there is no configuration available for the OA, get the default qaa level + setQaa(ConfigurationDBRead.getMOAIDConfiguration().getAuthComponentGeneral().getForeignIdentities().getSTORK().getQualityAuthenticationAssuranceLevel()); + } + + // prepare attribute helper list + attributes = new ArrayList<AttributeHelper>(); + for(StorkAttribute current : ConfigurationDBRead.getMOAIDConfiguration().getAuthComponentGeneral().getForeignIdentities().getSTORK().getAttributes()) { + AttributeHelper tmp = null; + + for(OAStorkAttribute sepp : config.getOAAttributes()) + if(sepp.getName().equals(current.getName())) + tmp = new AttributeHelper(sepp); + + if(null == tmp) + tmp = new AttributeHelper(current); + + attributes.add(tmp); + } + } + } + } + + public boolean isStorkLogonEnabled() { + return isStorkLogonEnabled; + } + + public void setStorkLogonEnabled(boolean enabled) { + this.isStorkLogonEnabled = enabled; + } + + public int getQaa() { + return qaa; + } + + public void setQaa(int qaa) { + this.qaa = qaa; + } + + public List<OAStorkAttribute> getAttributes() { + List<OAStorkAttribute> result = new ArrayList<OAStorkAttribute>(); + + if(null == getHelperAttributes()) + return result; + + for(AttributeHelper current : getHelperAttributes()) { + for(StorkAttribute currentAttribute : ConfigurationDBRead.getMOAIDConfiguration().getAuthComponentGeneral().getForeignIdentities().getSTORK().getAttributes()) + if(currentAttribute.getName().equals(current.getName())) { + if(current.isUsed() || currentAttribute.isMandatory()) { + OAStorkAttribute tmp = new OAStorkAttribute(); + tmp.setName(current.getName()); + tmp.setMandatory(current.isMandatory()); + result.add(tmp); + } + break; + } + } + + return result; + } + + public List<AttributeHelper> getHelperAttributes() { + return attributes; + } + + public void setHelperAttributes(List<AttributeHelper> attributes) { + this.attributes = attributes; + } } diff --git a/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/struts/action/EditGeneralConfigAction.java b/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/struts/action/EditGeneralConfigAction.java index 362579c9f..3c8c0e18d 100644 --- a/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/struts/action/EditGeneralConfigAction.java +++ b/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/struts/action/EditGeneralConfigAction.java @@ -72,8 +72,10 @@ import at.gv.egovernment.moa.id.commons.db.ex.MOADatabaseException; import at.gv.egovernment.moa.id.configuration.Constants; import at.gv.egovernment.moa.id.configuration.auth.AuthenticatedUser; import at.gv.egovernment.moa.id.configuration.data.GeneralMOAIDConfig; +import at.gv.egovernment.moa.id.configuration.data.GeneralStorkConfig; import at.gv.egovernment.moa.id.configuration.helper.LanguageHelper; import at.gv.egovernment.moa.id.configuration.validation.moaconfig.MOAConfigValidator; +import at.gv.egovernment.moa.id.configuration.validation.moaconfig.StorkConfigValidator; import at.gv.egovernment.moa.id.util.Random; import at.gv.egovernment.moa.util.MiscUtil; @@ -89,7 +91,8 @@ public class EditGeneralConfigAction extends ActionSupport private AuthenticatedUser authUser; private GeneralMOAIDConfig moaconfig; - + private GeneralStorkConfig storkconfig; + private String formID; public String loadConfig() { @@ -108,6 +111,8 @@ public class EditGeneralConfigAction extends ActionSupport moaconfig = new GeneralMOAIDConfig(); moaconfig.parse(dbconfig); + storkconfig = new GeneralStorkConfig(); + storkconfig.parse(dbconfig); ConfigurationDBUtils.closeSession(); @@ -152,6 +157,8 @@ public class EditGeneralConfigAction extends ActionSupport MOAConfigValidator validator = new MOAConfigValidator(); List<String> errors = validator.validate(moaconfig); + + errors.addAll(new StorkConfigValidator().validate(storkconfig)); if (errors.size() > 0) { log.info("General MOA-ID configuration has some erros."); @@ -429,10 +436,15 @@ public class EditGeneralConfigAction extends ActionSupport if (oldforeign != null) { STORK oldstork = oldforeign.getSTORK(); if (oldstork != null) - dbforeign.setSTORK(oldstork); + oldstork = new STORK(); + + oldstork.setQualityAuthenticationAssuranceLevel(storkconfig.getDefaultQaa()); + oldstork.setAttributes(storkconfig.getAttributes()); + oldstork.setCPEPS(storkconfig.getCpepslist()); + dbforeign.setSTORK(oldstork); } } - + if (MiscUtil.isNotEmpty(moaconfig.getMandateURL())) { OnlineMandates dbmandate = dbauth.getOnlineMandates(); if (dbmandate == null) { @@ -572,6 +584,24 @@ public class EditGeneralConfigAction extends ActionSupport public void setMoaconfig(GeneralMOAIDConfig moaconfig) { this.moaconfig = moaconfig; } + + /** + * Gets the storkconfig. + * + * @return the storkconfig + */ + public GeneralStorkConfig getStorkconfig() { + return storkconfig; + } + + /** + * Sets the storkconfig. + * + * @param storkconfig the new storkconfig + */ + public void setStorkconfig(GeneralStorkConfig storkconfig) { + this.storkconfig = storkconfig; + } /** * @return the formID diff --git a/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/struts/action/EditOAAction.java b/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/struts/action/EditOAAction.java index 5366aff23..775443689 100644 --- a/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/struts/action/EditOAAction.java +++ b/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/struts/action/EditOAAction.java @@ -60,6 +60,7 @@ import at.gv.egovernment.moa.id.commons.db.dao.config.OAOAUTH20; import at.gv.egovernment.moa.id.commons.db.dao.config.OAPVP2; import at.gv.egovernment.moa.id.commons.db.dao.config.OASAML1; import at.gv.egovernment.moa.id.commons.db.dao.config.OASSO; +import at.gv.egovernment.moa.id.commons.db.dao.config.OASTORK; import at.gv.egovernment.moa.id.commons.db.dao.config.OnlineApplication; import at.gv.egovernment.moa.id.commons.db.dao.config.OnlineApplicationType; import at.gv.egovernment.moa.id.commons.db.dao.config.STORK; @@ -123,7 +124,7 @@ public class EditOAAction extends ActionSupport implements ServletRequestAware, private OASAML1Config saml1OA = new OASAML1Config(); private OASSOConfig ssoOA = new OASSOConfig(); private OAOAuth20Config oauth20OA = new OAOAuth20Config(); - private OASTORKConfig storkOA; + private OASTORKConfig storkOA = new OASTORKConfig(); private FormularCustomization formOA = new FormularCustomization(); private InputStream stream; @@ -182,6 +183,8 @@ public class EditOAAction extends ActionSupport implements ServletRequestAware, saml1OA.parse(onlineapplication); oauth20OA.parse(onlineapplication); session.setAttribute(Constants.SESSION_OAUTH20SECRET, this.oauth20OA.getClientSecret()); + + storkOA.parse(onlineapplication); Map<String, String> map = new HashMap<String, String>(); map.putAll(FormBuildUtils.getDefaultMap()); @@ -698,7 +701,7 @@ public class EditOAAction extends ActionSupport implements ServletRequestAware, } } } - preview = FormBuildUtils.customiceLayoutBKUSelection(preview, true, false, map); + preview = FormBuildUtils.customiceLayoutBKUSelection(preview, true, false, map, true); } } else { @@ -948,12 +951,6 @@ public class EditOAAction extends ActionSupport implements ServletRequestAware, sso.setSingleLogOutURL(ssoOA.getSingleLogOutURL()); - STORK stork = authoa.getSTORK(); - if (stork == null) { - // TODO: make stork configurable - - } - if (oauth20OA != null) { log.debug("Saving OAuth 2.0 configuration:"); OAOAUTH20 oaOAuth20 = authoa.getOAOAUTH20(); @@ -974,6 +971,20 @@ public class EditOAAction extends ActionSupport implements ServletRequestAware, } + + // fetch stork configuration from database model + OASTORK stork = authoa.getOASTORK(); + if (stork == null) { + // if there is none, create a new one with default values. + stork = new OASTORK(); + authoa.setOASTORK(stork); + stork.setStorkLogonEnabled(false); + } + // transfer the incoming data to the database model + stork.setStorkLogonEnabled(storkOA.isStorkLogonEnabled()); + stork.setQaa(storkOA.getQaa()); + stork.setOAAttributes(storkOA.getAttributes()); + try { if (newentry) { ConfigurationDBUtils.save(dboa); diff --git a/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/struts/action/ImportExportAction.java b/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/struts/action/ImportExportAction.java index 9fa58ca1e..3bc2d4ac5 100644 --- a/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/struts/action/ImportExportAction.java +++ b/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/struts/action/ImportExportAction.java @@ -56,8 +56,6 @@ import at.gv.egovernment.moa.id.util.Random; import com.opensymphony.xwork2.ActionSupport; -import eu.stork.vidp.messages.common.STORKBootstrap; - public class ImportExportAction extends ActionSupport implements ServletRequestAware, ServletResponseAware { @@ -136,21 +134,7 @@ implements ServletRequestAware, ServletResponseAware { session.setAttribute(Constants.SESSION_FORMID, formID); return Constants.STRUTS_ERROR_VALIDATION; } - - //Initialize OpenSAML for STORK - log.info("Starting initialization of OpenSAML..."); - try { - STORKBootstrap.bootstrap(); - - } catch (org.opensaml.xml.ConfigurationException e1) { - log.info("Legacy configuration has an Import Error", e1); - addActionError(LanguageHelper.getErrorString("errors.importexport.legacyimport", new Object[] {e1.getMessage()})); - - formID = Random.nextRandom(); - session.setAttribute(Constants.SESSION_FORMID, formID); - return Constants.STRUTS_ERROR_VALIDATION; - } - log.debug("OpenSAML successfully initialized"); + try { MOAIDConfiguration moaidconfig = ConfigurationDBRead.getMOAIDConfiguration(); diff --git a/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/validation/moaconfig/StorkConfigValidator.java b/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/validation/moaconfig/StorkConfigValidator.java new file mode 100644 index 000000000..318b3b3e7 --- /dev/null +++ b/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/validation/moaconfig/StorkConfigValidator.java @@ -0,0 +1,89 @@ +package at.gv.egovernment.moa.id.configuration.validation.moaconfig; + +import java.util.ArrayList; +import java.util.List; +import org.apache.log4j.Logger; + +import at.gv.egovernment.moa.id.commons.db.dao.config.CPEPS; +import at.gv.egovernment.moa.id.commons.db.dao.config.StorkAttribute; +import at.gv.egovernment.moa.id.configuration.data.GeneralStorkConfig; +import at.gv.egovernment.moa.id.configuration.helper.LanguageHelper; +import at.gv.egovernment.moa.id.configuration.validation.ValidationHelper; +import at.gv.egovernment.moa.util.MiscUtil; + +public class StorkConfigValidator { + + private static final Logger log = Logger.getLogger(StorkConfigValidator.class); + + public List<String> validate(GeneralStorkConfig form) { + + List<String> errors = new ArrayList<String>(); + + log.debug("Validate general STORK configuration"); + + // check peps list + for(CPEPS current : form.getCpepslist()) { + // check country code + String check = current.getCountryCode(); + if (MiscUtil.isNotEmpty(check)) { + if (ValidationHelper.containsPotentialCSSCharacter(check, false)) { + log.warn("CPEPS config countrycode contains potentail XSS characters: " + check); + errors.add(LanguageHelper.getErrorString("validation.stork.cpeps.cc", + new Object[] {ValidationHelper.getPotentialCSSCharacter(false)} )); + } + if(!check.toLowerCase().matches("^[a-z][a-z]$")) { + log.warn("CPEPS config countrycode does not comply to ISO 3166-2 : " + check); + errors.add(LanguageHelper.getErrorString("validation.stork.cpeps.cc", + new Object[] {check} )); + } + } else { + log.warn("CPEPS config countrycode is empty : " + check); + errors.add(LanguageHelper.getErrorString("validation.stork.cpeps.empty", + new Object[] {check} )); + } + + // check url + check = current.getURL(); + if (MiscUtil.isNotEmpty(check)) { + if (!ValidationHelper.validateURL(check)) { + log.info("CPEPS config URL is invalid : " + check); + errors.add(LanguageHelper.getErrorString("validation.stork.cpeps.url")); + } + } else { + log.warn("CPEPS config url is empty : " + check); + errors.add(LanguageHelper.getErrorString("validation.stork.cpeps.empty", + new Object[] {check} )); + } + } + + // check qaa + int qaa = form.getDefaultQaa(); + if(1 > qaa && 4 < qaa) { + log.warn("QAA is out of range : " + qaa); + errors.add(LanguageHelper.getErrorString("validation.stork.qaa.outofrange", + new Object[] {qaa} )); + } + + // check attributes + if (MiscUtil.isNotEmpty(form.getAttributes())) { + for(StorkAttribute check : form.getAttributes()) { + if (ValidationHelper.containsPotentialCSSCharacter(check.getName(), true)) { + log.warn("default attributes contains potentail XSS characters: " + check); + errors.add(LanguageHelper.getErrorString("validation.stork.requestedattributes", + new Object[] {ValidationHelper.getPotentialCSSCharacter(true)} )); + } + if(!check.getName().toLowerCase().matches("^[a-z0-9]*$")) { + log.warn("default attributes do not match the requested format : " + check); + errors.add(LanguageHelper.getErrorString("validation.stork.requestedattributes", + new Object[] {check} )); + } + } + } else { + log.warn("no attributes specified"); + errors.add(LanguageHelper.getErrorString("validation.stork.attributes.empty", + new Object[] {} )); + } + + return errors; + } +} diff --git a/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/validation/oa/OASTORKConfigValidation.java b/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/validation/oa/OASTORKConfigValidation.java index def5aa5ed..f72999020 100644 --- a/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/validation/oa/OASTORKConfigValidation.java +++ b/id/ConfigWebTool/src/main/java/at/gv/egovernment/moa/id/configuration/validation/oa/OASTORKConfigValidation.java @@ -25,14 +25,27 @@ package at.gv.egovernment.moa.id.configuration.validation.oa; import java.util.ArrayList; import java.util.List; +import org.apache.log4j.Logger; + import at.gv.egovernment.moa.id.configuration.data.oa.OASTORKConfig; +import at.gv.egovernment.moa.id.configuration.helper.LanguageHelper; public class OASTORKConfigValidation { + + private static final Logger log = Logger.getLogger(OASTORKConfigValidation.class); + public List<String> validate(OASTORKConfig oageneral) { - + List<String> errors = new ArrayList<String>(); - - + + // check qaa + int qaa = oageneral.getQaa(); + if(1 > qaa && 4 < qaa) { + log.warn("QAA is out of range : " + qaa); + errors.add(LanguageHelper.getErrorString("validation.stork.qaa.outofrange", + new Object[] {qaa} )); + } + return errors; } } |