diff options
| author | Florian Reimair <florian.reimair@iaik.tugraz.at> | 2014-03-12 09:54:39 +0100 | 
|---|---|---|
| committer | Florian Reimair <florian.reimair@iaik.tugraz.at> | 2014-03-12 10:03:55 +0100 | 
| commit | 921a14d46078fba6ee66addd9b0c40ae82081f9c (patch) | |
| tree | 80a24b81497de3506ba6e6432e9a29c6432752f7 | |
| parent | 1c802614fe489280f93d36bfb6908ebffc96e4aa (diff) | |
| download | moa-id-spss-921a14d46078fba6ee66addd9b0c40ae82081f9c.tar.gz moa-id-spss-921a14d46078fba6ee66addd9b0c40ae82081f9c.tar.bz2 moa-id-spss-921a14d46078fba6ee66addd9b0c40ae82081f9c.zip | |
sketched consent collector
5 files changed, 598 insertions, 80 deletions
| diff --git a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/stork2/AttributeCollector.java b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/stork2/AttributeCollector.java index 9cd825fc8..5d972ba00 100644 --- a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/stork2/AttributeCollector.java +++ b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/stork2/AttributeCollector.java @@ -148,12 +148,7 @@ public class AttributeCollector implements IAction {  					// else, update any existing attributes  					addOrUpdateAll(container.getResponse().getPersonalAttributeList(), aquiredAttributes);              } - -            // build response -            generateSTORKResponse(container); - -            // set new http response -            generateRedirectResponse(response, container); +            	new ConsentEvaluatorSepp().requestConsent(container, response, oaParam);              return "12345"; // AssertionId @@ -186,79 +181,6 @@ public class AttributeCollector implements IAction {      }      /** -     * generates binary response from given response class. -     * -     * @param container the container -     * @throws MOAIDException the mOAID exception -     */ -    private void generateSTORKResponse(DataContainer container) throws MOAIDException { -    	MOASTORKRequest request = container.getRequest(); -        MOASTORKResponse response = container.getResponse(); - -        try { -            //Get SAMLEngine instance -            STORKSAMLEngine engine = STORKSAMLEngine.getInstance("VIDP"); -            Logger.debug("Starting generation of SAML response"); -			if(response.isAuthnResponse()) -				response.setSTORKAuthnResponse(engine.generateSTORKAuthnResponse(request.getStorkAuthnRequest(), response.getStorkAuthnResponse(), container.getRemoteAddress(), false)); -			else -				response.setSTORKAttrResponse(engine.generateSTORKAttrQueryResponse(request.getStorkAttrQueryRequest(), response.getStorkAttrQueryResponse(), container.getRemoteAddress(), "", false)); -				 -            //generateSAML Token -            Logger.info("SAML response succesfully generated!"); -        } catch (STORKSAMLEngineException e) { -            Logger.error("Failed to generate STORK SAML Response", e); -            throw new MOAIDException("stork.05", null); -        } - -        Logger.info("STORK SAML Response message succesfully generated "); -    } - -    /** -     * writes the storkresponse to the httpresponse using the velocity engine. -     * -     * @param httpResp the http resp -     * @param container the container -     */ -    private void generateRedirectResponse(HttpServletResponse httpResp, DataContainer container) { -        MOASTORKResponse authnResponse = container.getResponse(); -        MOASTORKRequest authnRequest = container.getRequest(); - -        // preparing redirection for the client -        try { -            VelocityEngine velocityEngine = VelocityProvider.getClassPathVelocityEngine(); -            Template template = velocityEngine.getTemplate("/resources/templates/stork2_postbinding_template.html"); -            VelocityContext context = new VelocityContext(); -             -            byte[] blob; -			if(authnRequest.isAttrRequest()) -            	blob = authnResponse.getStorkAttrQueryResponse().getTokenSaml(); -            else -            	blob = authnResponse.getStorkAuthnResponse().getTokenSaml(); - -            context.put("SAMLResponse", PEPSUtil.encodeSAMLToken(blob)); -            Logger.debug("SAMLResponse original: " + new String(blob)); - -            Logger.debug("Putting assertion consumer url as action: " + authnRequest.getAssertionConsumerServiceURL()); -            context.put("action", authnRequest.getAssertionConsumerServiceURL()); -            Logger.debug("Starting template merge"); -            StringWriter writer = new StringWriter(); - -            Logger.debug("Doing template merge"); -            template.merge(context, writer); -            Logger.debug("Template merge done"); - -            Logger.debug("Sending html content: " + writer.getBuffer().toString()); -            Logger.debug("Sending html content2  : " + new String(writer.getBuffer())); - -            httpResp.getOutputStream().write(writer.getBuffer().toString().getBytes()); - -        } catch (Exception e) { -            Logger.error("Velocity error: " + e.getMessage()); -        } -    } -     -    /**       * Adds or updates all {@link PersonalAttribute} objects given in {@code source} to/in {@code target}.       *       * @param target the target diff --git a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/stork2/ConsentEvaluator.java b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/stork2/ConsentEvaluator.java new file mode 100644 index 000000000..9745d81c5 --- /dev/null +++ b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/stork2/ConsentEvaluator.java @@ -0,0 +1,149 @@ +package at.gv.egovernment.moa.id.protocols.stork2; + +import java.io.StringWriter; +import at.gv.egovernment.moa.id.auth.data.AuthenticationSession; +import at.gv.egovernment.moa.id.auth.exception.MOAIDException; +import at.gv.egovernment.moa.id.auth.stork.VelocityProvider; +import at.gv.egovernment.moa.id.commons.db.ex.MOADatabaseException; +import at.gv.egovernment.moa.id.config.auth.OAAuthParameter; +import at.gv.egovernment.moa.id.moduls.IAction; +import at.gv.egovernment.moa.id.moduls.IRequest; +import at.gv.egovernment.moa.id.storage.AssertionStorage; +import at.gv.egovernment.moa.logging.Logger; +import eu.stork.peps.auth.commons.PEPSUtil; +import eu.stork.peps.auth.engine.STORKSAMLEngine; +import eu.stork.peps.exceptions.STORKSAMLEngineException; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.VelocityEngine; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * The ConsentEvaluator assists with fetching user consent on the list of attributes to be sent to the asking S-PEPS. + */ +public class ConsentEvaluator implements IAction { + +    /** +     * The Constant ARTIFACT_ID. +     */ +    private static final String ARTIFACT_ID = "artifactId"; + +    /* (non-Javadoc) +     * @see at.gv.egovernment.moa.id.moduls.IAction#processRequest(at.gv.egovernment.moa.id.moduls.IRequest, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, at.gv.egovernment.moa.id.auth.data.AuthenticationSession) +     */ +    public String processRequest(IRequest req, HttpServletRequest httpReq, HttpServletResponse httpResp, AuthenticationSession moasession) throws MOAIDException { + +		// - fetch the container +		String artifactId = (String) httpReq.getParameter(ARTIFACT_ID); +		DataContainer container; +		try { +			container = AssertionStorage.getInstance().get(artifactId, DataContainer.class); +		} catch (MOADatabaseException e) { +			Logger.error("Error fetching incomplete Stork response from temporary storage. Most likely a timeout occured.", e); +			throw new MOAIDException("stork.17", null); +		} + +		// TODO evaluate response + +        // build and send response +        generateSTORKResponse(httpResp, container); +         +        return "12345"; // AssertionId +    } + +	/** +	 * Fills the given HttpResponse with the required web page. +	 * +	 * @param container the container +	 * @param response the response +	 * @param oaParam the oa param +	 * @return the string +	 * @throws MOAIDException the mOAID exception +	 */ +	public String requestConsent(DataContainer container, HttpServletResponse response, OAAuthParameter oaParam) throws MOAIDException { +		// prepare redirect + +		// ask for consent + +		return "12345"; // AssertionId +	} + +    /** +     * generates binary response from given response class and fill the given HttpResponse with a SAML Post Binding template. +     * +     * @param httpResp the http resp +     * @param container the container +     * @throws MOAIDException the mOAID exception +     */ +    public void generateSTORKResponse(HttpServletResponse httpResp, DataContainer container) throws MOAIDException { +    	MOASTORKRequest request = container.getRequest(); +        MOASTORKResponse response = container.getResponse(); + +        try { +            //Get SAMLEngine instance +            STORKSAMLEngine engine = STORKSAMLEngine.getInstance("VIDP"); +            Logger.debug("Starting generation of SAML response"); +			if(response.isAuthnResponse()) +				response.setSTORKAuthnResponse(engine.generateSTORKAuthnResponse(request.getStorkAuthnRequest(), response.getStorkAuthnResponse(), container.getRemoteAddress(), false)); +			else +				response.setSTORKAttrResponse(engine.generateSTORKAttrQueryResponse(request.getStorkAttrQueryRequest(), response.getStorkAttrQueryResponse(), container.getRemoteAddress(), "", false)); +				 +            //generateSAML Token +            Logger.info("SAML response succesfully generated!"); +        } catch (STORKSAMLEngineException e) { +            Logger.error("Failed to generate STORK SAML Response", e); +            throw new MOAIDException("stork.05", null); +        } + +        Logger.info("STORK SAML Response message succesfully generated "); + +        // preparing redirection for the client +        try { +            VelocityEngine velocityEngine = VelocityProvider.getClassPathVelocityEngine(); +            Template template = velocityEngine.getTemplate("/resources/templates/stork2_postbinding_template.html"); +            VelocityContext context = new VelocityContext(); +             +            byte[] blob; +			if(request.isAttrRequest()) +            	blob = response.getStorkAttrQueryResponse().getTokenSaml(); +            else +            	blob = response.getStorkAuthnResponse().getTokenSaml(); + +            context.put("SAMLResponse", PEPSUtil.encodeSAMLToken(blob)); +            Logger.debug("SAMLResponse original: " + new String(blob)); + +            Logger.debug("Putting assertion consumer url as action: " + request.getAssertionConsumerServiceURL()); +            context.put("action", request.getAssertionConsumerServiceURL()); +            Logger.debug("Starting template merge"); +            StringWriter writer = new StringWriter(); + +            Logger.debug("Doing template merge"); +            template.merge(context, writer); +            Logger.debug("Template merge done"); + +            Logger.debug("Sending html content: " + writer.getBuffer().toString()); +            Logger.debug("Sending html content2  : " + new String(writer.getBuffer())); + +            httpResp.getOutputStream().write(writer.getBuffer().toString().getBytes()); + +        } catch (Exception e) { +            Logger.error("Velocity error: " + e.getMessage()); +        } +    } + +    /* (non-Javadoc) +     * @see at.gv.egovernment.moa.id.moduls.IAction#needAuthentication(at.gv.egovernment.moa.id.moduls.IRequest, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) +     */ +    public boolean needAuthentication(IRequest req, HttpServletRequest httpReq, HttpServletResponse httpResp) { +    	// this action does not need any authentication. The authentication is already done by the preceding AuthenticationRequest-Action. +        return false; +    } + +    /* (non-Javadoc) +     * @see at.gv.egovernment.moa.id.moduls.IAction#getDefaultActionName() +     */ +    public String getDefaultActionName() { +        return STORKProtocol.CONSENT_EVALUATOR; +    } +} diff --git a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/stork2/STORKProtocol.java b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/stork2/STORKProtocol.java index e415daf3e..b1c923b9f 100644 --- a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/stork2/STORKProtocol.java +++ b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/stork2/STORKProtocol.java @@ -26,12 +26,14 @@ public class STORKProtocol implements IModulInfo, MOAIDAuthConstants {      public static final String AUTHENTICATIONREQUEST = "AuthenticationRequest";      public static final String ATTRIBUTE_COLLECTOR = "AttributeCollector";      public static final String MANDATERETRIEVALREQUEST = "MandateRetrievalRequest"; +	public static final String CONSENT_EVALUATOR = "ConsentEvaluator";      private static HashMap<String, IAction> actions = new HashMap<String, IAction>();      static {          actions.put(AUTHENTICATIONREQUEST, new AuthenticationRequest());          actions.put(ATTRIBUTE_COLLECTOR, new AttributeCollector()); +        actions.put(CONSENT_EVALUATOR, new ConsentEvaluatorSepp());      }      public String getName() { @@ -63,7 +65,7 @@ public class STORKProtocol implements IModulInfo, MOAIDAuthConstants {          MOASTORKRequest STORK2Request = new MOASTORKRequest(); -		if (AttributeCollector.class.getSimpleName().equals(action)) +		if (AttributeCollector.class.getSimpleName().equals(action) || ConsentEvaluatorSepp.class.getSimpleName().equals(action))  			return STORK2Request;          //extract STORK Response from HTTP Request diff --git a/id/server/idserverlib/src/main/resources/resources/properties/id_messages_de.properties b/id/server/idserverlib/src/main/resources/resources/properties/id_messages_de.properties index f6a296fde..d45abbf1c 100644 --- a/id/server/idserverlib/src/main/resources/resources/properties/id_messages_de.properties +++ b/id/server/idserverlib/src/main/resources/resources/properties/id_messages_de.properties @@ -211,6 +211,7 @@ stork.13=Fehler beim Sammeln eines Attributes in einem AttributProviderPlugin  stork.14=Es wurde weder Authentifizierungs/  noch Attributerequest empfangen
  stork.15=Unbekannte request.
  stork.16=Ein Attribute aus zwei verschiedenen Quellen unterscheidet sich\: {0}
 +stork.17=Fehler beim Einholen der Zustimmung für Attribut\u00FCbertragung durch den Benutzer
  pvp2.00={0} ist kein gueltiger consumer service index
  pvp2.01=Fehler beim kodieren der PVP2 Antwort
 diff --git a/id/server/idserverlib/src/main/resources/resources/templates/stork2_consent.html b/id/server/idserverlib/src/main/resources/resources/templates/stork2_consent.html new file mode 100644 index 000000000..2ad03e34e --- /dev/null +++ b/id/server/idserverlib/src/main/resources/resources/templates/stork2_consent.html @@ -0,0 +1,444 @@ +<!DOCTYPE html> +<html> +<head> +<meta content="text/html; charset=utf-8" http-equiv="Content-Type"> + +   <!-- MOA-ID 2.x BKUSelection Layout CSS -->                +    <style type="text/css"> +			@media screen and (min-width: 650px) { +			 +				body { +					margin:0; +					padding:0; +					color : #000; +					background-color : #fff; +			  	text-align: center; +			  	background-color: #6B7B8B; +				} +         +        #bku_header h2 { +          font-size: 0.8em; +        }  +         +         +			  #page { +			    display: block; +			    border: 2px solid rgb(0,0,0); +			    width: 650px; +			    height: 460px; +			    margin: 0 auto; +			    margin-top: 5%; +			    position: relative; +			    border-radius: 25px; +			    background: rgb(255,255,255); +			  } +			   +			  #page1 { +			    text-align: center; +			  } +			   +			  #main { +			    /*	clear:both; */ +				  position:relative; +			    margin: 0 auto; +			    width: 250px; +			    text-align: center; +			  } +			   +			  .OA_header { +			/*	  background-color: white;*/ +			    font-size: 20pt; +			    margin-bottom: 25px; +			    margin-top: 25px; +			  } +			 +			  #leftcontent { +			    /*float:left; */ +				  width:250px; +				  margin-bottom: 25px; +			    text-align: left; +			    border: 1px solid rgb(0,0,0); +			  } +			  			   +			  #selectArea { +				 font-size: 15px; +				 padding-bottom: 65px; +			  } +			 +			  #leftcontent { +				 width: 300px; +				 margin-top: 30px; +			  } +			 +        #bku_header { +          height: 5%; +          padding-bottom: 3px; +          padding-top: 3px; +        } +       +        #bkulogin { +				  overflow:auto;	 +          min-width: 190px; +          height: 260px; +			  } +       +        h2#tabheader{ +				  font-size: 1.1em;  +          padding-left: 2%; +          padding-right: 2%; +          position: relative; +			  } +        		   +			  .setAssertionButton_full { +			  	background: #efefef; +				  cursor: pointer; +				  margin-top: 15px; +			    width: 100px; +			    height: 30px +			  } +			 +			  #leftbutton  { +				 width: 30%;  +				 float:left;  +				 margin-left: 40px; +			  } +			 +			  #rightbutton { +				 width: 30%;  +				 float:right;  +				 margin-right: 45px;  +				 text-align: right; +			  } +         +        button { +          height: 25px; +          width: 75px; +          margin-bottom: 10px; +        } +         +       #validation { +        position: absolute; +        bottom: 0px; +        margin-left: 270px; +        padding-bottom: 10px; +      } +			 +			} + +      @media screen and (max-width: 205px) {         +        #bku_header h2 { +          font-size: 0.8em; +          margin-top: -0.4em; +          padding-top: 0.4em; +        } +         +        #bkulogin { +        min-height: 150px; +        }  +      } + +      @media screen and (max-width: 249px) and (min-width: 206px) {         +        #bku_header h2 { +          font-size: 0.9em; +          margin-top: -0.45em; +          padding-top: 0.45em; +        } +         +        #bkulogin { +          height: 180px; +        }   +      } + +      @media screen and (max-width: 299px) and (min-width: 250px) { +        #bku_header h2 { +          font-size: 1.1em; +          margin-top: -0.55em; +          padding-top: 0.55em; +        }  +      } +       +      @media screen and (max-width: 649px) and (min-width: 400px) { +        #bku_header h2 { +          font-size: 1.3em; +          margin-top: -0.65em; +          padding-top: 0.65em; +        }  +      } + + +			 +			@media screen and (max-width: 649px) { +				 +        body { +					margin:0; +					padding:0; +					color : #000; +			  	text-align: center; +          font-size: 100%; +			  	background-color: #MAIN_BACKGOUNDCOLOR#; +				} +        				 +			  #page { +			     visibility: hidden; +			     margin-top: 0%; +			  } +			   +			  #page1 { +			    visibility: hidden; +			  } +			   +			  #main { +			    visibility: hidden; +			  } +         +        #validation { +          visibility: hidden; +          display: none; +        } +			   +			  .OA_header { +			    margin-bottom: 0px; +			    margin-top: 0px; +			    font-size: 0pt; +			    visibility: hidden; +			  } +			 +			  #leftcontent { +			    visibility: visible; +			    margin-bottom: 0px; +			    text-align: left; +			    border:none; +          vertical-align: middle; +          min-height: 173px; +          min-width: 204px; +           +			  } +			   +        #bku_header { +          height: 10%; +          min-height: 1.2em; +          margin-top: 1%; +        } +         +        h2#tabheader{ +          padding-left: 2%; +          padding-right: 2%; +          position: relative; +          top: 50%; +			  } +         +       	#bkulogin {	 +          min-width: 190px; +          height: 155px;	 +			 } +         +			 .setAssertionButton_full { +			     	background: #efefef; +				    cursor: pointer; +				    margin-top: 15px; +			      width: 70px; +			      height: 25px; +			 } +        +        input[type=button] { +/*          height: 11%;  */ +          width: 70%; +        } +			} +			       +			* { +				margin: 0; +				padding: 0; +        font-family: #FONTTYPE#; +			} +							      			 +			#selectArea { +				padding-top: 10px; +				padding-bottom: 55px; +				padding-left: 10px; +			} +			 +			.setAssertionButton { +				background: #efefef; +				cursor: pointer; +				margin-top: 15px; +			  width: 70px; +			  height: 25px; +			} +			 +			#leftbutton  { +				width: 35%;  +				float:left;  +				margin-left: 15px; +			} +			 +			#rightbutton { +				width: 35%;  +				float:right;  +				margin-right: 25px;  +				text-align: right; +			} +       +      .verticalcenter { +        vertical-align: middle; +      } +       +			input { +				/*border:1px solid #000;*/ +				cursor: pointer; +			} +       + +			#installJava, #BrowserNOK { +				clear:both; +				font-size:0.8em; +				padding:4px; +			} +						 +			.selectText{ +			 +			} +			 +			.selectTextHeader{ +			 +			} +			 +			.sendButton { +        width: 30%; +        margin-bottom: 1%;	 +			} +			 +			#leftcontent a { +				text-decoration:none;  +				color: #000; +			/*	display:block;*/ +				padding:4px;	 +			} +			 +			#leftcontent a:hover, #leftcontent a:focus, #leftcontent a:active { +				text-decoration:underline; +				color: #000;	 +			} +						 +			.infobutton { +				background-color: #005a00; +				color: white; +				font-family: serif; +				text-decoration: none; +				padding-top: 2px; +				padding-right: 4px; +				padding-bottom: 2px; +				padding-left: 4px; +				font-weight: bold; +			} +			 +			.hell { +				background-color : #MAIN_BACKGOUNDCOLOR#; +        color: #MAIN_COLOR#;	 +			} +			 +			.dunkel { +				background-color: #HEADER_BACKGROUNDCOLOR#; +        color: #HEADER_COLOR#; +			} +			       +			.main_header { +			   color: black; +			    font-size: 32pt; +			    position: absolute; +			    right: 10%; +			    top: 40px; +				 +			} +			 +			#controls { +				text-align: right; +			} +      			                         +    </style>        +<!-- MOA-ID 2.x BKUSelection JavaScript fucnctions--> +<script type="text/javascript"> +		function isIE() { +			return (/MSIE (\d+\.\d+);/.test(navigator.userAgent)); +		} +		function isFullscreen() { +			try { +				return ((top.innerWidth == screen.width) && (top.innerHeight == screen.height)); +			} catch (e) { +				return false; +			} +		} +		function isActivexEnabled() { +			var supported = null; +			try { +				supported = !!new ActiveXObject("htmlfile"); +			} catch (e) { +				supported = false; +			} +			return supported; +		} +		function generateIFrame(iFrameURL) { +			var el = document.getElementById("bkulogin"); +      var width = el.clientWidth; +      var heigth = el.clientHeight - 20; +			var parent = el.parentNode; +             +      iFrameURL += "&heigth=" + heigth; +      iFrameURL += "&width=" + width; +       +			var iframe = document.createElement("iframe"); +			iframe.setAttribute("src", iFrameURL); +			iframe.setAttribute("width", el.clientWidth - 1); +			iframe.setAttribute("height", el.clientHeight - 1); +			iframe.setAttribute("frameborder", "0"); +			iframe.setAttribute("scrolling", "no"); +			iframe.setAttribute("title", "Login"); +			parent.replaceChild(iframe, el); +		} +		function onChangeChecks() { +      if (top.innerWidth < 650) { +         document.getElementById("moaidform").setAttribute("target","_parent"); +      } else { +         document.getElementById("moaidform").removeAttribute("target"); +      } +       +    } +	</script> +<title>#HEADER_TEXT#</title> +</head> +<body onload="onChangeChecks();" onresize="onChangeChecks();"> +	<div id="page"> +		<div id="page1" class="case selected-case" role="main"> +			<h2 class="OA_header" role="heading">STORK Informationsfreigabe</h2> +			<div id="main"> +				<div id="leftcontent" class="hell" role="application"> +					<form method="POST" action="${action}"> +						<div id="bku_header" class="dunkel"> +							<h2 id="tabheader" class="dunkel" role="heading">STORK Informationsfreigabe</h2> +						</div> +						<div id="bkulogin" class="hell" role="form"> +							Alle angehakten Daten werden an das fragende Drittland übermittelt. +	  						<table> +	  							${tablecontent} +							</table> +						</div> +						<div id="controls" class="hell"> +							<input type="submit" value="weiter" /> +						</div> +					</form> +				</div> +			</div> +		</div> +		<div id="validation"> +			<a href="http://validator.w3.org/check?uri="> <img +				style="border: 0; width: 88px; height: 31px" +				src="#CONTEXTPATH#/img/valid-html5-blue.png" alt="HTML5 ist valide!" /> +			</a> <a href="http://jigsaw.w3.org/css-validator/"> <img +				style="border: 0; width: 88px; height: 31px" +				src="http://jigsaw.w3.org/css-validator/images/vcss-blue" +				alt="CSS ist valide!" /> +			</a> +		</div> +	</div> +</body> +</html
\ No newline at end of file | 
