From a27a770b6a2db10b0b13ee6d6517028b5736729c Mon Sep 17 00:00:00 2001 From: Michael Hirsch Date: Thu, 28 Jan 2016 10:35:26 +0100 Subject: [PATCH 1/7] removing the default value from the @Value annotation, seems like is not working overwriting the default value with profiles. Removing the default value, it is possible again to overwrite the default value again. --- .../java/org/eclipse/hawkbit/security/SecurityProperties.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityProperties.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityProperties.java index b9bf45fd0..e6742ebc8 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityProperties.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityProperties.java @@ -31,7 +31,7 @@ public class SecurityProperties { @Value("${hawkbit.server.controller.security.rp.trustedIPs:#{null}}") private List rpTrustedIPs; - @Value("${hawkbit.server.controller.security.authentication.anonymous.enabled:false}") + @Value("${hawkbit.server.controller.security.authentication.anonymous.enabled}") private Boolean anonymousEnabled; public String getRpCnHeader() { From 877cb1ee24754c3fce4d68dce75f5f7984619f80 Mon Sep 17 00:00:00 2001 From: SirWayne Date: Thu, 28 Jan 2016 15:29:31 +0100 Subject: [PATCH 2/7] Remove @Value annotation and use inner configuration properties to set the default value. Signed-off-by: SirWayne --- .../hawkbit/security/SecurityProperties.java | 112 +++++++++++++----- 1 file changed, 84 insertions(+), 28 deletions(-) diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityProperties.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityProperties.java index e6742ebc8..d3c426977 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityProperties.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityProperties.java @@ -10,8 +10,9 @@ package org.eclipse.hawkbit.security; import java.util.List; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; /** * The common properties for security. @@ -22,47 +23,102 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties public class SecurityProperties { - @Value("${hawkbit.server.controller.security.rp.cnHeader:X-Ssl-Client-Cn}") - private String rpCnHeader; + @Component + @ConfigurationProperties("hawkbit.server.controller.security.rp") + public static class RpProperties { + private String cnHeader = "X-Ssl-Client-Cn"; + private String sslIssuerHashHeader = "X-Ssl-Issuer-Hash-%d"; + private List trustedIPs; - @Value("${hawkbit.server.controller.security.rp.sslIssuerHashHeader:X-Ssl-Issuer-Hash-%d}") - private String rpSslIssuerHashHeader; + /** + * @return the cnHeader + */ + public String getCnHeader() { + return cnHeader; + } - @Value("${hawkbit.server.controller.security.rp.trustedIPs:#{null}}") - private List rpTrustedIPs; + /** + * @param cnHeader + * the cnHeader to set + */ + public void setCnHeader(final String cnHeader) { + this.cnHeader = cnHeader; + } - @Value("${hawkbit.server.controller.security.authentication.anonymous.enabled}") - private Boolean anonymousEnabled; + /** + * @return the sslIssuerHashHeader + */ + public String getSslIssuerHashHeader() { + return sslIssuerHashHeader; + } + + /** + * @param sslIssuerHashHeader + * the sslIssuerHashHeader to set + */ + public void setSslIssuerHashHeader(final String sslIssuerHashHeader) { + this.sslIssuerHashHeader = sslIssuerHashHeader; + } + + /** + * @return the trustedIPs + */ + public List getTrustedIPs() { + return trustedIPs; + } + + /** + * @param trustedIPs + * the trustedIPs to set + */ + public void setTrustedIPs(final List trustedIPs) { + this.trustedIPs = trustedIPs; + } + + } + + @Component + @ConfigurationProperties("hawkbit.server.controller.security.authentication") + public static class AuthenticationsProperties { + private Boolean anonymousEnabled = Boolean.FALSE; + + /** + * @param anonymousEnabled + * the anonymousEnabled to set + */ + public void setAnonymousEnabled(final Boolean anonymousEnabled) { + this.anonymousEnabled = anonymousEnabled; + } + + /** + * @return the anonymousEnabled + */ + public Boolean getAnonymousEnabled() { + return anonymousEnabled; + } + + } + + @Autowired + private RpProperties rppProperties; + + @Autowired + private AuthenticationsProperties authenticationsProperties; public String getRpCnHeader() { - return rpCnHeader; + return rppProperties.getCnHeader(); } public String getRpSslIssuerHashHeader() { - return rpSslIssuerHashHeader; + return rppProperties.getSslIssuerHashHeader(); } public List getRpTrustedIPs() { - return rpTrustedIPs; + return rppProperties.getTrustedIPs(); } public Boolean getAnonymousEnabled() { - return anonymousEnabled; + return authenticationsProperties.getAnonymousEnabled(); } - public void setRpCnHeader(final String rpCnHeader) { - this.rpCnHeader = rpCnHeader; - } - - public void setRpSslIssuerHashHeader(final String rpSslIssuerHashHeader) { - this.rpSslIssuerHashHeader = rpSslIssuerHashHeader; - } - - public void setRpTrustedIPs(final List rpTrustedIPs) { - this.rpTrustedIPs = rpTrustedIPs; - } - - public void setAnonymousEnabled(final Boolean anonymousEnabled) { - this.anonymousEnabled = anonymousEnabled; - } } From bc46bac2ac587468659f5c8bb0ddf487ea30f6c0 Mon Sep 17 00:00:00 2001 From: Kai Zimmermann Date: Tue, 2 Feb 2016 10:19:37 +0100 Subject: [PATCH 3/7] Fix sonar issue --- .../targettable/BulkUploadHandler.java | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/BulkUploadHandler.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/BulkUploadHandler.java index fa4022a3e..0716e6bd6 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/BulkUploadHandler.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/BulkUploadHandler.java @@ -207,39 +207,12 @@ public class BulkUploadHandler extends CustomComponent @Override public void run() { - long innerCounter = 0; - String line; if (tempFile == null) { return; } try (InputStream tempStream = new FileInputStream(tempFile)) { - try (BufferedReader reader = new BufferedReader( - new InputStreamReader(tempStream, Charset.defaultCharset()))) { - LOG.info("Bulk file upload started"); - final double totalFileSize = getTotalNumberOfLines(); - - /** - * Once control is in upload succeeded method automatically - * upload button is re-enabled. To disable the button firing - * below event. - */ - eventBus.publish(this, new TargetTableEvent(TargetComponentEvent.BULK_UPLOAD_PROCESS_STARTED)); - while ((line = reader.readLine()) != null) { - innerCounter++; - readEachLine(line, innerCounter, totalFileSize); - } - doAssignments(); - eventBus.publish(this, new TargetTableEvent(TargetComponentEvent.BULK_UPLOAD_COMPLETED)); - - // Clearing after assignments are done - managementUIState.getTargetTableFilters().getBulkUpload().getTargetsCreated().clear(); - } catch (final IOException e) { - LOG.error("Error reading file {}", tempFile.getName(), e); - } finally { - resetCounts(); - deleteFile(); - } + readFileStream(tempStream); } catch (final FileNotFoundException e) { LOG.error("Temporary file not found with name {}", tempFile.getName(), e); } catch (final IOException e) { @@ -248,6 +221,37 @@ public class BulkUploadHandler extends CustomComponent } + private void readFileStream(final InputStream tempStream) { + String line; + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(tempStream, Charset.defaultCharset()))) { + LOG.info("Bulk file upload started"); + long innerCounter = 0; + final double totalFileSize = getTotalNumberOfLines(); + + /** + * Once control is in upload succeeded method automatically + * upload button is re-enabled. To disable the button firing + * below event. + */ + eventBus.publish(this, new TargetTableEvent(TargetComponentEvent.BULK_UPLOAD_PROCESS_STARTED)); + while ((line = reader.readLine()) != null) { + innerCounter++; + readEachLine(line, innerCounter, totalFileSize); + } + doAssignments(); + eventBus.publish(this, new TargetTableEvent(TargetComponentEvent.BULK_UPLOAD_COMPLETED)); + + // Clearing after assignments are done + managementUIState.getTargetTableFilters().getBulkUpload().getTargetsCreated().clear(); + } catch (final IOException e) { + LOG.error("Error reading file {}", tempFile.getName(), e); + } finally { + resetCounts(); + deleteFile(); + } + } + private void doAssignments() { final StringBuilder errorMessage = new StringBuilder(); String dsAssignmentFailedMsg = null; From d55e52170162fbe82428d4c67333661ed71da7ab Mon Sep 17 00:00:00 2001 From: Michael Hirsch Date: Tue, 2 Feb 2016 17:32:10 +0100 Subject: [PATCH 4/7] fixing security properties for anonymous enabled, adding javadoc Signed-off-by: Michael Hirsch --- .../hawkbit/security/SecurityProperties.java | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityProperties.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityProperties.java index d3c426977..8cc056f15 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityProperties.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityProperties.java @@ -23,6 +23,9 @@ import org.springframework.stereotype.Component; @ConfigurationProperties public class SecurityProperties { + /** + * Inner class for reverse proxy configuration. + */ @Component @ConfigurationProperties("hawkbit.server.controller.security.rp") public static class RpProperties { @@ -77,24 +80,27 @@ public class SecurityProperties { } + /** + * Inner class for anonymous enable configuration. + */ @Component - @ConfigurationProperties("hawkbit.server.controller.security.authentication") - public static class AuthenticationsProperties { - private Boolean anonymousEnabled = Boolean.FALSE; + @ConfigurationProperties("hawkbit.server.controller.security.authentication.anonymous") + public static class AnoymousAuthenticationProperties { + private Boolean enabled = Boolean.FALSE; /** - * @param anonymousEnabled - * the anonymousEnabled to set + * @param enabled + * the enabled to set */ - public void setAnonymousEnabled(final Boolean anonymousEnabled) { - this.anonymousEnabled = anonymousEnabled; + public void setEnabled(final Boolean enabled) { + this.enabled = enabled; } /** - * @return the anonymousEnabled + * @return the enabled */ - public Boolean getAnonymousEnabled() { - return anonymousEnabled; + public Boolean getEnabled() { + return enabled; } } @@ -103,7 +109,7 @@ public class SecurityProperties { private RpProperties rppProperties; @Autowired - private AuthenticationsProperties authenticationsProperties; + private AnoymousAuthenticationProperties authenticationsProperties; public String getRpCnHeader() { return rppProperties.getCnHeader(); @@ -118,7 +124,7 @@ public class SecurityProperties { } public Boolean getAnonymousEnabled() { - return authenticationsProperties.getAnonymousEnabled(); + return authenticationsProperties.getEnabled(); } } From b81b37a988d17ad3f0cb5bc5d5160daf3089031d Mon Sep 17 00:00:00 2001 From: Michael Hirsch Date: Wed, 3 Feb 2016 09:35:07 +0100 Subject: [PATCH 5/7] - extend simulator rest-api for simulating also DDI devices - adapt readme.md to document new simulator rest-api - remove basic-auth from standard application properties and introduce an extra cloud-profile which includes basic-auth-security parameters for easier use locally - fix dialog for amount with 4 digit and comma charachter Signed-off-by: Michael Hirsch --- examples/hawkbit-device-simulator/README.md | 13 ++++++-- .../simulator/SimulationController.java | 31 ++++++++++++++++--- .../hawkbit/simulator/ui/GenerateDialog.java | 7 +++-- .../resources/application-cloud.properties | 19 ++++++++++++ .../src/main/resources/application.properties | 20 +----------- 5 files changed, 61 insertions(+), 29 deletions(-) create mode 100644 examples/hawkbit-device-simulator/src/main/resources/application-cloud.properties diff --git a/examples/hawkbit-device-simulator/README.md b/examples/hawkbit-device-simulator/README.md index 610a1d256..869f80511 100644 --- a/examples/hawkbit-device-simulator/README.md +++ b/examples/hawkbit-device-simulator/README.md @@ -13,7 +13,7 @@ run org.eclipse.hawkbit.simulator.DeviceSimulator ## Notes -The simulator has user authentication enabled by default. Default credentials: +The simulator has user authentication enabled in **cloud profile**. Default credentials: * username : admin * passwd : admin @@ -29,8 +29,6 @@ The UI can be accessed via the URL: http://localhost:8083 ``` -`Basic Authentication Credentials are admin / admin` - ![](src/main/images/generateScreenshot.png) ![](src/main/images/updateProcessScreenshot.png) @@ -45,6 +43,10 @@ Optional parameters: * name : name prefix simulated devices (default: "dmfSimulated"), followed by counter * amount : number of simulated devices (default: 20, capped at: 4000) * tenant : in a multi-tenenat ready hawkBit installation (default: "DEFAULT") +* api : the API which should be used for the simulated device either `dmf` or `ddi` (default: "ddi") +* endpoint : URL which defines the hawkbit DDI base endpoint (deffault: "http://localhost:8080") +* polldelay : number in milliseconds of the delay when DDI simulated devices should poll the endpoint (default: "30") +* gatewaytoken : an hawkbit gateway token to be used in case hawkbit does not allow anonymous access for DDI devices (default: "") Example: for 20 simulated devices (default) @@ -56,3 +58,8 @@ Example: for 10 simulated devices that start with the name prefix "activeSim": ``` http://localhost:8083/start?amount=10&name=activeSim ``` + +Example: for 5 simulated devices that start with the name prefix "ddi" using the Direct Device Integration API (http): +``` +http://localhost:8083/start?amount=5&name=ddi?api=ddi +``` diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java index 6f94bd319..4606f86bd 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java @@ -8,9 +8,13 @@ */ package org.eclipse.hawkbit.simulator; +import java.net.MalformedURLException; +import java.net.URL; + import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol; import org.eclipse.hawkbit.simulator.amqp.SpSenderService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -43,18 +47,37 @@ public class SimulationController { * @param tenant * the tenant to create the device to * @return a response string that devices has been created + * @throws MalformedURLException */ @RequestMapping("/start") - String start(@RequestParam(value = "name", defaultValue = "dmfSimulated") final String name, + ResponseEntity start(@RequestParam(value = "name", defaultValue = "simulated") final String name, @RequestParam(value = "amount", defaultValue = "20") final int amount, - @RequestParam(value = "tenant", defaultValue = "DEFAULT") final String tenant) { + @RequestParam(value = "tenant", defaultValue = "DEFAULT") final String tenant, + @RequestParam(value = "api", defaultValue = "dmf") final String api, + @RequestParam(value = "endpoint", defaultValue = "http://localhost:8080") final String endpoint, + @RequestParam(value = "polldelay", defaultValue = "30") final int pollDelay, + @RequestParam(value = "gatewaytoken", defaultValue = "") final String gatewayToken) + throws MalformedURLException { + + final Protocol protocol; + switch (api.toLowerCase()) { + case "dmf": + protocol = Protocol.DMF_AMQP; + break; + case "ddi": + protocol = Protocol.DDI_HTTP; + break; + default: + return ResponseEntity.badRequest().body("query param api only allows value of 'dmf' or 'ddi'"); + } for (int i = 0; i < amount; i++) { final String deviceId = name + i; - repository.add(deviceFactory.createSimulatedDevice(deviceId, tenant, Protocol.DMF_AMQP)); + repository.add(deviceFactory.createSimulatedDevice(deviceId, tenant, protocol, pollDelay, new URL(endpoint), + gatewayToken)); spSenderService.createOrUpdateThing(tenant, deviceId); } - return "Updated " + amount + " DMF connected targets!"; + return ResponseEntity.ok("Updated " + amount + " DMF connected targets!"); } } diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java index 1400ec0e1..e4509e082 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java @@ -83,8 +83,8 @@ public class GenerateDialog extends Window { tf5.setIcon(FontAwesome.FLAG_O); tf5.setRequired(true); tf5.setVisible(false); - tf5.addValidator(new RegexpValidator( - "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]", "is not an URL")); + tf5.addValidator(new RegexpValidator("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]", + "is not an URL")); final TextField tf6 = new TextField("gateway token", ""); tf6.setColumns(50); @@ -125,7 +125,8 @@ public class GenerateDialog extends Window { @Override public void buttonClick(final ClickEvent event) { try { - callback.okButton(tf1.getValue(), tf3.getValue(), Integer.valueOf(tf2.getValue().replace(".", "")), + callback.okButton(tf1.getValue(), tf3.getValue(), + Integer.valueOf(tf2.getValue().replace(".", "").replace(",", "")), Integer.valueOf(tf4.getValue().replace(".", "")), new URL(tf5.getValue()), tf6.getValue(), (Protocol) protocolGroup.getValue()); } catch (final NumberFormatException e) { diff --git a/examples/hawkbit-device-simulator/src/main/resources/application-cloud.properties b/examples/hawkbit-device-simulator/src/main/resources/application-cloud.properties new file mode 100644 index 000000000..f4f41821c --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/resources/application-cloud.properties @@ -0,0 +1,19 @@ +# SECURITY (SecurityProperties) +security.basic.enabled=true +security.user.name=${BASIC_USERNAME:admin} +security.user.password=${BASIC_PASSWORD:admin} +security.user.role=USER +security.require-ssl=false +security.enable-csrf=false +security.basic.enabled=true +security.basic.realm=DeviceSimulator +security.basic.path= /** +security.basic.authorize-mode=ROLE +security.filter-order=0 +security.headers.xss=false +security.headers.cache=false +security.headers.frame=false +security.headers.content-type=false +security.headers.hsts=all +security.sessions=stateless +security.ignored=/VAADIN/** \ No newline at end of file diff --git a/examples/hawkbit-device-simulator/src/main/resources/application.properties b/examples/hawkbit-device-simulator/src/main/resources/application.properties index 402f71bfe..56d0190a7 100644 --- a/examples/hawkbit-device-simulator/src/main/resources/application.properties +++ b/examples/hawkbit-device-simulator/src/main/resources/application.properties @@ -27,23 +27,5 @@ spring.rabbitmq.port=5672 spring.rabbitmq.dynamic=true spring.rabbitmq.listener.prefetch=100 -# SECURITY (SecurityProperties) -security.user.name=${BASIC_USERNAME:admin} -security.user.password=${BASIC_PASSWORD:admin} -security.user.role=USER -security.require-ssl=false -security.enable-csrf=false -security.basic.enabled=true -security.basic.realm=DeviceSimulator -security.basic.path= /** -security.basic.authorize-mode=ROLE -security.filter-order=0 -security.headers.xss=false -security.headers.cache=false -security.headers.frame=false -security.headers.content-type=false -security.headers.hsts=all -security.sessions=stateless -security.ignored=/VAADIN/** - +security.basic.enabled=false server.port=8083 From e35017cfe7ff3022e93eb3d30522c052f2503109 Mon Sep 17 00:00:00 2001 From: Michael Hirsch Date: Wed, 3 Feb 2016 09:59:13 +0100 Subject: [PATCH 6/7] fix license header in application-cloud.properties file Signed-off-by: Michael Hirsch --- .../src/main/resources/application-cloud.properties | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/examples/hawkbit-device-simulator/src/main/resources/application-cloud.properties b/examples/hawkbit-device-simulator/src/main/resources/application-cloud.properties index f4f41821c..3e3edc90c 100644 --- a/examples/hawkbit-device-simulator/src/main/resources/application-cloud.properties +++ b/examples/hawkbit-device-simulator/src/main/resources/application-cloud.properties @@ -1,3 +1,12 @@ +# +# Copyright (c) 2015 Bosch Software Innovations GmbH and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# + # SECURITY (SecurityProperties) security.basic.enabled=true security.user.name=${BASIC_USERNAME:admin} From 9ff1e897176c70c9046205421138c008b9d91953 Mon Sep 17 00:00:00 2001 From: Michael Hirsch Date: Wed, 3 Feb 2016 10:34:52 +0100 Subject: [PATCH 7/7] add javadoc parameter to simulatorcontroller Signed-off-by: Michael Hirsch --- .../hawkbit/simulator/SimulationController.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java index 4606f86bd..c1f358d89 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java @@ -46,6 +46,17 @@ public class SimulationController { * the amount of devices to be created * @param tenant * the tenant to create the device to + * @param api + * the api-protocol to be used either {@code dmf} or {@code ddi} + * @param endpoint + * the URL endpoint to be used of the hawkbit-update-server for + * DDI devices + * @param pollDelay + * number of delay in milliseconds to delay polling of DDI + * devices + * @param gatewayToken + * the hawkbit-update-server gatwaytoken in case authentication + * is enforced in hawkbit * @return a response string that devices has been created * @throws MalformedURLException */