+Spring Security 4 added support for securing http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html[Spring's WebSocket support].
+This section describes how to use Spring Security's WebSocket support.
+
+NOTE: You can find a complete working sample of WebSocket security in samples/chat-jc.
+
+.Direct JSR-356 Support
+****
+Spring Security does not provide direct JSR-356 support because doing so would provide little value.
+This is because the format is unknown, so there is http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-intro-sub-protocol[little Spring can do to secure an unknown format].
+Additionally, JSR-356 does not provide a way to intercept messages, so security would be rather invasive.
+****
+
+[[websocket-configuration]]
+=== WebSocket Configuration
+
+Spring Security 4.0 has introduced authorization support for WebSockets through the Spring Messaging abstraction.
+To configure authorization using Java Configuration, simply extend the `AbstractSecurityWebSocketMessageBrokerConfigurer` and configure the `MessageSecurityMetadataSourceRegistry`.
+<1> Any inbound CONNECT message requires a valid CSRF token to enforce <<websocket-sameorigin,Same Origin Policy>>
+<2> The SecurityContextHolder is populated with the user within the simpUser header attribute for any inbound request.
+<3> Our messages require the proper authorization. Specifically, any inbound message that starts with "/user/" will require ROLE_USER. Additional details on authorization can be found in <<websocket-authorization>>
+
+Spring Security also provides <<nsa-websocket,XML Namespace>> support for securing WebSockets.
+A comparable XML based configuration looks like the following:
+<1> Any inbound CONNECT message requires a valid CSRF token to enforce <<websocket-sameorigin,Same Origin Policy>>
+<2> The SecurityContextHolder is populated with the user within the simpUser header attribute for any inbound request.
+<3> Our messages require the proper authorization. Specifically, any inbound message that starts with "/user/" will require ROLE_USER. Additional details on authorization can be found in <<websocket-authorization>>
+
+[[websocket-authentication]]
+=== WebSocket Authentication
+
+WebSockets reuse the same authentication information that is found in the HTTP request when the WebSocket connection was made.
+This means that the `Principal` on the `HttpServletRequest` will be handed off to WebSockets.
+If you are using Spring Security, the `Principal` on the `HttpServletRequest` is overridden automatically.
+
+More concretely, to ensure a user has authenticated to your WebSocket application, all that is necessary is to ensure that you setup Spring Security to authenticate your HTTP based web application.
+
+[[websocket-authorization]]
+=== WebSocket Authorization
+
+Spring Security 4.0 has introduced authorization support for WebSockets through the Spring Messaging abstraction.
+To configure authorization using Java Configuration, simply extend the `AbstractSecurityWebSocketMessageBrokerConfigurer` and configure the `MessageSecurityMetadataSourceRegistry`.
+For example:
+
+[source,java]
+----
+@Configuration
+public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
+<1> Any message without a destination (i.e. anything other that Message type of MESSAGE or SUBSCRIBE) will require the user to be authenticated
+<2> Anyone can subscribe to /user/queue/errors
+<3> Any message that has a destination starting with "/app/" will be require the user to have the role ROLE_USER
+<4> Any message that starts with "/user/" or "/topic/friends/" that is of type SUBSCRIBE will require ROLE_USER
+<5> Any other message of type MESSAGE or SUBSCRIBE is rejected. Due to 6 we do not need this step, but it illustrates how one can match on specific message types.
+<6> Any other Message is rejected. This is a good idea to ensure that you do not miss any messages.
+
+Spring Security also provides <<nsa-websocket,XML Namespace>> support for securing WebSockets.
+A comparable XML based configuration looks like the following:
+<1> Any message of type CONNECT, UNSUBSCRIBE, or DISCONNECT will require the user to be authenticated
+<2> Anyone can subscribe to /user/queue/errors
+<3> Any message that has a destination starting with "/app/" will be require the user to have the role ROLE_USER
+<4> Any message that starts with "/user/" or "/topic/friends/" that is of type SUBSCRIBE will require ROLE_USER
+<5> Any other message of type MESSAGE or SUBSCRIBE is rejected. Due to 6 we do not need this step, but it illustrates how one can match on specific message types.
+<6> Any other message with a destination is rejected. This is a good idea to ensure that you do not miss any messages.
+
+[[websocket-authorization-notes]]
+==== WebSocket Authorization Notes
+
+In order to properly secure your application it is important to understand Spring's WebSocket support.
+
+[[websocket-authorization-notes-messagetypes]]
+===== WebSocket Authorization on Message Types
+
+It is important to understand the distinction between SUBSCRIBE and MESSAGE types of messages and how it works within Spring.
+
+Consider a chat application.
+
+* The system can send notifications MESSAGE to all users through a destination of "/topic/system/notifications"
+* Clients can receive notifications by SUBSCRIBE to the "/topic/system/notifications".
+
+While we want clients to be able to SUBSCRIBE to "/topic/system/notifications", we do not want to enable them to send a MESSAGE to that destination.
+If we allowed sending a MESSAGE to "/topic/system/notifications", then clients could send a message directly to that endpoint and impersonate the system.
+
+In general, it is common for applications to deny any MESSAGE sent to a message that starts with the http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp[broker prefix] (i.e. "/topic/" or "/queue/").
+
+[[websocket-authorization-notes-destinations]]
+===== WebSocket Authorization on Destinations
+
+It is also is important to understand how destinations are transformed.
+
+Consider a chat application.
+
+* User's can send messages to a specific user by sending a message to the destination of "/app/chat".
+* The application sees the message, ensures that the "from" attribute is specified as the current user (we cannot trust the client).
+* The application then sends the message to the recipient using `SimpMessageSendingOperations.convertAndSendToUser("toUser", "/queue/messages", message)`.
+* The message gets turned into the destination of "/queue/user/messages-<sessionid>"
+
+With the application above, we want to allow our client to listen to "/user/queue" which is transformed into "/queue/user/messages-<sessionid>".
+However, we do not want the client to be able to listen to "/queue/*" because that would allow the client to see messages for every user.
+
+In general, it is common for applications to deny any SUBSCRIBE sent to a message that starts with the http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp[broker prefix] (i.e. "/topic/" or "/queue/").
+Of course we may provide exceptions to account for things like
+
+[[websocket-authorization-notes-outbound]]
+==== Outbound Messages
+
+Spring contains a section titled http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-message-flow[Flow of Messages] that describes how messages flow through the system.
+It is important to note that Spring Security only secures the `clientInboundChannel`.
+Spring Security does not attempt to secure the `clientOutboundChannel`.
+
+The most important reason for this is performance.
+For every message that goes in, there are typically many many more that go out.
+Instead of securing the outbound messages, we encourage securing the subscription to the endpoints.
+
+[[websocket-sameorigin]]
+=== Enforcing Same Origin Policy
+
+It is important to emphasize that the browser does not enforce the http://en.wikipedia.org/wiki/Same-origin_policy[Same Origin Policy] for WebSocket connections.
+This is an extremely important consideration.
+
+[[websocket-sameorigin-why]]
+==== Why Same Origin?
+
+Consider the following scenario.
+A user visits bank.com and authenticates to their account.
+The same user opens another tab in their browser and visits evil.com.
+The Same Origin Policy ensures that evil.com cannot read or write data to bank.com.
+
+With WebSockets the Same Origin Policy does not apply.
+In fact, unless bank.com explicitly forbids it, evil.com can read and write data on behalf of the user.
+This means that anything the user can do over the websocket (i.e. transfer money), evil.com can do on that users behalf.
+
+Since SockJS tries to emulate WebSockets it also bypasses the Same Origin Policy.
+This means developers need to explicitly protect their applications from external domains when using SockJS.
+
+[[websocket-sameorigin-spring]]
+==== Spring WebSocket Allowed Origin
+
+Fortunately, since Spring 4.1.5 Spring's WebSocket and SockJS support restricts access to the http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-server-allowed-origins[current domain].
+Spring Security adds an additional layer of protection to provide http://en.wikipedia.org/wiki/Defense_in_depth_%28computing%29[defence in depth].
+
+[[websocket-sameorigin-csrf]]
+==== Adding CSRF to Stomp Headers
+
+By default Spring Security requires the <<csrf,CSRF token>> in any CONNECT message type.
+This ensures that only a site that has access to the CSRF token can connect.
+Since only the *Same Origin* can access the CSRF token, external domains are not allowed to make a connection.
+
+Typically we need to include the CSRF token in an HTTP header or an HTTP parameter.
+However, SockJS does not allow for these options.
+Instead, we must include the token in the Stomp headers
+
+Applications can <<csrf-include-csrf-token,obtain a CSRF token>> by accessing the request attribute named _csrf.
+For example, the following will allow accessing the `CsrfToken` in a JSP:
+
+[source,javascript]
+----
+var headerName = "${_csrf.headerName}";
+var token = "${_csrf.token}";
+----
+
+If you are using static HTML, you can expose the `CsrfToken` on a REST endpoint.
+For example, the following would expose the `CsrfToken` on the URL /csrf
+
+[source,java]
+----
+@RestController
+public class CsrfController {
+
+ @RequestMapping("/csrf")
+ public CsrfToken csrf(CsrfToken token) {
+ return token;
+ }
+}
+----
+
+The javascript can make a REST call to the endpoint and use the response to populate the headerName and the token.
+
+We can now include the token in our Stomp client.
+For example:
+
+[source,javascript]
+----
+...
+var headers = {};
+headers[headerName] = token;
+stompClient.connect(headers, function(frame) {
+ ...
+
+}
+----
+
+[[websocket-sameorigin-disable]]
+==== Disable CSRF within WebSockets
+
+If you want to allow other domains to access your site, you can disable Spring Security's protection.
+For example, in Java Configuration you can use the following:
+
+[source,java]
+----
+@Configuration
+public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
+
+ ...
+
+ @Override
+ protected boolean sameOriginEnforced() {
+ return false;
+ }
+}
+----
+
+
+[[websocket-sockjs]]
+=== Working with SockJS
+
+http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-fallback[SockJS] provides fallback transports to support older browsers.
+When using the fallback options we need to relax a few security constraints to allow SockJS to work with Spring Security.
+
+[[websocket-sockjs-sameorigin]]
+==== SockJS & frame-options
+
+SockJS may use an https://github.com/sockjs/sockjs-client/tree/v0.3.4[transport that leverages an iframe].
+By default Spring Security will <<headers-frame-options,deny>> the site from being framed to prevent Clickjacking attacks.
+To allow SockJS frame based transports to work, we need to configure Spring Security to allow the same origin to frame the content.
+
+You can customize X-Frame-Options with the <<nsa-frame-options,frame-options>> element.
+For example, the following will instruct Spring Security to use "X-Frame-Options: SAMEORIGIN" which allows iframes within the same domain:
+
+[source,xml]
+----
+<http>
+ <!-- ... -->
+
+ <headers>
+ <frame-options
+ policy="SAMEORIGIN" />
+ </headers>
+</http>
+----
+
+Similarly, you can customize frame options to use the same origin within Java Configuration using the following:
+SockJS uses a POST on the CONNECT messages for any HTTP based transport.
+Typically we need to include the CSRF token in an HTTP header or an HTTP parameter.
+However, SockJS does not allow for these options.
+Instead, we must include the token in the Stomp headers as described in <<websocket-sameorigin-csrf>>.
+
+It also means we need to relax our CSRF protection with the web layer.
+Specifically, we want to disable CSRF protection for our connect URLs.
+We do NOT want to disable CSRF protection for every URL.
+Otherwise our site will be vulnerable to CSRF attacks.
+
+We can easily achieve this by providing a CSRF RequestMatcher.
+Our Java Configuration makes this extremely easy.
+For example, if our stomp endpoint is "/chat" we can disable CSRF protection for only URLs that start with "/chat/" using the following configuration:
@@ -4065,6 +4065,7 @@ Rounding out the anonymous authentication discussion is the `AuthenticationTrust
You will often see the `ROLE_ANONYMOUS` attribute in the above interceptor configuration replaced with `IS_AUTHENTICATED_ANONYMOUSLY`, which is effectively the same thing when defining access controls. This is an example of the use of the `AuthenticatedVoter` which we will see in the <<authz-authenticated-voter,authorization chapter>>. It uses an `AuthenticationTrustResolver` to process this particular configuration attribute and grant access to anonymous users. the `AuthenticatedVoter` approach is more powerful, since it allows you to differentiate between anonymous, remember-me and fully-authenticated users. If you don't need this functionality though, then you can stick with `ROLE_ANONYMOUS`, which will be processed by Spring Security's standard `RoleVoter`.
+This version of Bootstrap is maintained to be only the built version of bootstrap to be used with bower. If you are looking for the full source of bootstrap go to [bootstrap](https://github.com/twitter/bootstrap)
+
+Bootstrap is a sleek, intuitive, and powerful front-end framework for faster and easier web development, created and maintained by [Mark Otto](http://twitter.com/mdo) and [Jacob Thornton](http://twitter.com/fat) at Twitter.
+
+To get started, checkout http://getbootstrap.com!
+
+
+
+Quick start
+-----------
+
+Clone the repo, `git clone git://github.com/twitter/bootstrap.git`, or [download the latest release](https://github.com/twitter/bootstrap/zipball/master).
+
+
+
+Versioning
+----------
+
+For transparency and insight into our release cycle, and for striving to maintain backward compatibility, Bootstrap will be maintained under the Semantic Versioning guidelines as much as possible.
+
+Releases will be numbered with the following format:
+
+`<major>.<minor>.<patch>`
+
+And constructed with the following guidelines:
+
+* Breaking backward compatibility bumps the major (and resets the minor and patch)
+* New additions without breaking backward compatibility bumps the minor (and resets the patch)
+* Bug fixes and misc changes bumps the patch
+
+For more information on SemVer, please visit http://semver.org/.
+
+
+
+Bug tracker
+-----------
+
+Have a bug? Please create an issue here on GitHub that conforms with [necolas's guidelines](https://github.com/necolas/issue-guidelines).
+
+https://github.com/twitter/bootstrap/issues
+
+
+
+Twitter account
+---------------
+
+Keep up to date on announcements and more by following Bootstrap on Twitter, [@TwBootstrap](http://twitter.com/TwBootstrap).
+
+
+
+Blog
+----
+
+Read more detailed announcements, discussions, and more on [The Official Twitter Bootstrap Blog](http://blog.getbootstrap.com).
+
+
+
+Mailing list
+------------
+
+Have a question? Ask on our mailing list!
+
+twitter-bootstrap@googlegroups.com
+
+http://groups.google.com/group/twitter-bootstrap
+
+
+
+IRC
+---
+
+Server: irc.freenode.net
+
+Channel: ##twitter-bootstrap (the double ## is not a typo)
+
+
+
+Developers
+----------
+
+We have included a makefile with convenience methods for working with the Bootstrap library.
+
++ **dependencies**
+Our makefile depends on you having recess, connect, uglify.js, and jshint installed. To install, just run the following command in npm:
+
+```
+$ npm install recess connect uglify-js jshint -g
+```
+
++ **build** - `make`
+Runs the recess compiler to rebuild the `/less` files and compiles the docs pages. Requires recess and uglify-js. <a href="http://twitter.github.com/bootstrap/less.html#compiling">Read more in our docs »</a>
+
++ **test** - `make test`
+Runs jshint and qunit tests headlessly in [phantomjs](http://code.google.com/p/phantomjs/) (used for ci). Depends on having phantomjs installed.
+
++ **watch** - `make watch`
+This is a convenience method for watching just Less files and automatically building them whenever you save. Requires the Watchr gem.
+
+
+
+Contributing
+------------
+
+Please submit all pull requests against *-wip branches. If your unit test contains javascript patches or features, you must include relevant unit tests. Thanks!
+
+
+
+Authors
+-------
+
+**Mark Otto**
+
++ http://twitter.com/mdo
++ http://github.com/markdotto
+
+**Jacob Thornton**
+
++ http://twitter.com/fat
++ http://github.com/fat
+
+
+
+Copyright and license
+---------------------
+
+Copyright 2012 Twitter, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this work except in compliance with the License.
+You may obtain a copy of the License in the LICENSE file, or at:
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ for (var i = 0, j = nodesToReplaceArray.length; i < j; i++) {
+ ko.removeNode(nodesToReplaceArray[i]);
+ }
+ }
+ },
+
+ setOptionNodeSelectionState: function (optionNode, isSelected) {
+ // IE6 sometimes throws "unknown error" if you try to write to .selected directly, whereas Firefox struggles with setAttribute. Pick one based on browser.
+ // Workaround for IE9 rendering bug - it doesn't reliably display all the text in dynamically-added select boxes unless you force it to re-render by updating the width.
+ // (See https://github.com/SteveSanderson/knockout/issues/312, http://stackoverflow.com/questions/5908494/select-only-shows-first-char-of-selected-option)
+ // Also fixes IE7 and IE8 bug that causes selects to be zero width if enclosed by 'if' or 'with'. (See issue #839)
+ if (ieVersion) {
+ var originalWidth = selectElement.style.width;
+ selectElement.style.width = 0;
+ selectElement.style.width = originalWidth;
+ }
+ },
+
+ range: function (min, max) {
+ min = ko.utils.unwrapObservable(min);
+ max = ko.utils.unwrapObservable(max);
+ var result = [];
+ for (var i = min; i <= max; i++)
+ result.push(i);
+ return result;
+ },
+
+ makeArray: function(arrayLikeObject) {
+ var result = [];
+ for (var i = 0, j = arrayLikeObject.length; i < j; i++) {
+ result.push(arrayLikeObject[i]);
+ };
+ return result;
+ },
+
+ isIe6 : isIe6,
+ isIe7 : isIe7,
+ ieVersion : ieVersion,
+
+ getFormFields: function(form, fieldName) {
+ var fields = ko.utils.makeArray(form.getElementsByTagName("input")).concat(ko.utils.makeArray(form.getElementsByTagName("textarea")));
+ var isMatchingField = (typeof fieldName == 'string')
+ : function(field) { return fieldName.test(field.name) }; // Treat fieldName as regex or object containing predicate
+ var matches = [];
+ for (var i = fields.length - 1; i >= 0; i--) {
+ if (isMatchingField(fields[i]))
+ matches.push(fields[i]);
+ };
+ return matches;
+ },
+
+ parseJson: function (jsonString) {
+ if (typeof jsonString == "string") {
+ jsonString = ko.utils.stringTrim(jsonString);
+ if (jsonString) {
+ if (JSON && JSON.parse) // Use native parsing where available
+ return JSON.parse(jsonString);
+ return (new Function("return " + jsonString))(); // Fallback on less safe parsing for older browsers
+ }
+ }
+ return null;
+ },
+
+ stringifyJson: function (data, replacer, space) { // replacer and space are optional
+ if (!JSON || !JSON.stringify)
+ throw new Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js");
+ko.exportSymbol('unwrap', ko.utils.unwrapObservable); // Convenient shorthand, because this is used so commonly
+
+if (!Function.prototype['bind']) {
+ // Function.prototype.bind is a standard part of ECMAScript 5th Edition (December 2009, http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf)
+ // In case the browser doesn't implement it natively, provide a JavaScript implementation. This implementation is based on the one in prototype.js
+ Function.prototype['bind'] = function (object) {
+ node.nodeValue = ""; // Neuter this node so we don't try to unmemoize it again
+ if (node.parentNode)
+ node.parentNode.removeChild(node); // If possible, erase it totally (not always possible - someone else might just hold a reference to it then call unmemoizeDomNodeAndDescendants again)
+ }
+ },
+
+ parseMemoText: function (memoText) {
+ var match = memoText.match(/^\[ko_memo\:(.*?)\]$/);
+ throw new Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");
+ko.exportSymbol('computed', ko.dependentObservable); // Make "ko.computed" an alias for "ko.dependentObservable"
+ko.exportSymbol('isComputed', ko.isComputed);
+
+(function() {
+ var maxNestedObservableDepth = 10; // Escape the (unlikely) pathalogical case where an observable's current value is itself (or similar reference cycle)
+
+ ko.toJS = function(rootObject) {
+ if (arguments.length == 0)
+ throw new Error("When calling ko.toJS, pass the object you want to convert.");
+
+ // We just unwrap everything at every level in the object graph
+ if (binding && typeof binding["init"] == "function") {
+ var handlerInitFn = binding["init"];
+ var initResult = handlerInitFn(node, makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel, bindingContextInstance);
+
+ // If this binding handler claims to control descendant bindings, make a note of this
+ if (initResult && initResult['controlsDescendantBindings']) {
+ if (bindingHandlerThatControlsDescendantBindings !== undefined)
+ throw new Error("Multiple bindings (" + bindingHandlerThatControlsDescendantBindings + " and " + bindingKey + ") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");
+ ko.utils.registerEventHandler(element, "focusout", handleElementFocusOut); // For IE
+ },
+ 'update': function(element, valueAccessor) {
+ var value = !!ko.utils.unwrapObservable(valueAccessor()); //force boolean to compare with last value
+ if (!element[hasfocusUpdatingProperty] && element[hasfocusLastValue] !== value) {
+ value ? element.focus() : element.blur();
+ ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, value ? "focusin" : "focusout"]); // For IE, which doesn't reliably fire "focus" or "blur" events synchronously
+ }
+ }
+};
+
+ko.bindingHandlers['hasFocus'] = ko.bindingHandlers['hasfocus']; // Make "hasFocus" an alias
+ko.bindingHandlers['html'] = {
+ 'init': function() {
+ // Prevent binding on the dynamically-injected HTML (as developers are unlikely to expect that, and it has security implications)
+ var memoizeDataBindingAttributeSyntaxRegex = /(<([a-z]+\d*)(?:\s+(?!data-bind\s*=\s*)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi;
+ var memoizeVirtualContainerBindingSyntaxRegex = /<!--\s*ko\b\s*([\s\S]*?)\s*-->/g;
+
+ function validateDataBindValuesForRewriting(keyValueArray) {
+ var allValidators = ko.expressionRewriting.bindingRewriteValidators;
+ for (var i = 0; i < keyValueArray.length; i++) {
+ var key = keyValueArray[i]['key'];
+ if (allValidators.hasOwnProperty(key)) {
+ var validator = allValidators[key];
+
+ if (typeof validator === "function") {
+ var possibleErrorMessage = validator(keyValueArray[i]['value']);
+ if (possibleErrorMessage)
+ throw new Error(possibleErrorMessage);
+ } else if (!validator) {
+ throw new Error("This template engine does not support the '" + key + "' binding within its templates");
+ }
+ }
+ }
+ }
+
+ function constructMemoizedTagReplacement(dataBindAttributeValue, tagToRetain, nodeName, templateEngine) {
+ var dataBindKeyValueArray = ko.expressionRewriting.parseObjectLiteral(dataBindAttributeValue);
+ ko.renderTemplate = function (template, dataOrBindingContext, options, targetNodeOrNodeArray, renderMode) {
+ options = options || {};
+ if ((options['templateEngine'] || _templateEngine) == undefined)
+ throw new Error("Set a template engine before calling renderTemplate");
+ renderMode = renderMode || "replaceChildren";
+
+ if (targetNodeOrNodeArray) {
+ var firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
+
+ var whenToDispose = function () { return (!firstTargetNode) || !ko.utils.domNodeIsAttachedToDocument(firstTargetNode); }; // Passive disposal (on next evaluation)
+ // Call setDomNodeChildrenFromArrayMapping, ignoring any observables unwrapped within (most likely from a callback function).
+ // If the array items are observables, though, they will be unwrapped in executeTemplateForArrayItem and managed within setDomNodeChildrenFromArrayMapping.
+a.a.Oa(a.e.childNodes(b),!0)),h?(l||a.e.P(b,a.a.Oa(m.fb)),a.Ja(f?f(k,e):k,b)):a.e.ba(b),m.vb=h}};a.g.S[b]=!1;a.e.L[b]=!0}function J(b,c,d){d&&c!==a.h.n(b)&&a.h.W(b,c);c!==a.h.n(b)&&a.q.I(a.a.Ga,null,[b,"change"])}var a="undefined"!==typeof C?C:{};a.b=function(b,c){for(var d=b.split("."),f=a,g=0;g<d.length-1;g++)f=f[d[g]];f[d[d.length-1]]=c};a.r=function(a,c,d){a[c]=d};a.version="2.3.0";a.b("version",a.version);a.a=function(){function b(a,b){for(var e in a)a.hasOwnProperty(e)&&b(e,a[e])}function c(b,
+a.a.C.ia(b,function(){b.detachEvent(p,n)})}else throw Error("Browser doesn't support addEventListener or attachEvent");else b.addEventListener(d,k,!1);else{if(c(b,d)){var r=k;k=function(a,b){var e=this.checked;b&&(this.checked=!0!==b.sb);r.call(this,a);this.checked=e}}t(b).bind(d,k)}},Ga:function(a,b){if(!a||!a.nodeType)throw Error("element must be a DOM node when calling triggerEvent");if("undefined"!=typeof t){var e=[];c(a,b)&&e.push({sb:a.checked});t(a).trigger(b,e)}else if("function"==typeof s.createEvent)if("function"==
+typeof a.dispatchEvent)e=s.createEvent(f[b]||"HTMLEvents"),e.initEvent(b,!0,!0,w,0,0,0,0,0,!1,!1,!1,!1,0,a),a.dispatchEvent(e);else throw Error("The supplied element doesn't support dispatchEvent");else if("undefined"!=typeof a.fireEvent)c(a,b)&&(a.checked=!0!==a.checked),a.fireEvent("on"+b);else throw Error("Browser doesn't support triggering events");},c:function(b){return a.T(b)?b():b},ya:function(b){return a.T(b)?b.t():b},ga:function(b,e,c){if(e){var d=/\S+/g,g=b.className.match(d)||[];a.a.p(e.match(d),
+y&&y.parse?y.parse(b):(new Function("return "+b))():null},Ca:function(b,e,c){if(!y||!y.stringify)throw Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js");return y.stringify(a.a.c(b),e,c)},Ob:function(e,c,d){d=d||{};var g=d.params||{},f=d.includeFields||this.Ta,p=e;if("object"==typeof e&&"form"===a.a.u(e))for(var p=e.action,r=f.length-1;0<=r;r--)for(var z=
+a.a.Ua(e,f[r]),D=z.length-1;0<=D;D--)g[z[D].name]=z[D].value;c=a.a.c(c);var q=s.createElement("form");q.style.display="none";q.action=p;q.method="post";for(var v in c)e=s.createElement("input"),e.name=v,e.value=a.a.Ca(a.a.c(c[v])),q.appendChild(e);b(g,function(a,b){var e=s.createElement("input");e.name=a;e.value=b;q.appendChild(e)});s.body.appendChild(q);d.submitter?d.submitter(q):q.submit();setTimeout(function(){q.parentNode.removeChild(q)},0)}}}();a.b("utils",a.a);a.b("utils.arrayForEach",a.a.p);
+b(e,!1);if(d)for(var d=d.slice(0),f=0;f<d.length;f++)d[f](e);a.a.f.clear(e);"function"==typeof t&&"function"==typeof t.cleanData&&t.cleanData([e]);if(g[e.nodeType])for(d=e.firstChild;e=d;)d=e.nextSibling,8===e.nodeType&&c(e)}var d="__ko_domNodeDisposal__"+(new Date).getTime(),f={1:!0,8:!0,9:!0},g={1:!0,9:!0};return{ia:function(a,c){if("function"!=typeof c)throw Error("Callback must be a function");b(a,!0).push(c)},cb:function(e,c){var g=b(e,!1);g&&(a.a.ka(g,c),0==g.length&&a.a.f.set(e,d,q))},H:function(b){if(f[b.nodeType]&&
+0,e=c.childNodes,m=e.length;g<m;g++)b(e[g],f)}var c={};return{va:function(a){if("function"!=typeof a)throw Error("You can only pass a function to ko.memoization.memoize()");var b=(4294967296*(1+Math.random())|0).toString(16).substring(1)+(4294967296*(1+Math.random())|0).toString(16).substring(1);c[b]=a;return"\x3c!--[ko_memo:"+b+"]--\x3e"},mb:function(a,b){var g=c[a];if(g===q)throw Error("Couldn't find any memo with ID "+a+". Perhaps it's already been unmemoized.");try{return g.apply(null,b||[]),
+a.Wa=function(a){return null!=a&&"function"==typeof a.Da&&"function"==typeof a.notifySubscribers};a.b("subscribable",a.V);a.b("isSubscribable",a.Wa);a.q=function(){var b=[];return{rb:function(a){b.push({la:a,Ra:[]})},end:function(){b.pop()},bb:function(c){if(!a.Wa(c))throw Error("Only subscribable things can act as dependencies");if(0<b.length){var d=b[b.length-1];!d||0<=a.a.k(d.Ra,c)||(d.Ra.push(c),d.la(c))}},I:function(a,d,f){try{return b.push(null),a.apply(d,f||[])}finally{b.pop()}}}}();var L=
+a||typeof a in L?a===c:!1}};var A=a.m.Pb="__ko_proto__";a.m.fn[A]=a.m;a.qa=function(b,c){return null===b||b===q||b[A]===q?!1:b[A]===c?!0:a.qa(b[A],c)};a.T=function(b){return a.qa(b,a.m)};a.Xa=function(b){return"function"==typeof b&&b[A]===a.m||"function"==typeof b&&b[A]===a.j&&b.Eb?!0:!1};a.b("observable",a.m);a.b("isObservable",a.T);a.b("isWriteableObservable",a.Xa);a.U=function(b){b=b||[];if("object"!=typeof b||!("length"in b))throw Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");
+"beforeChange");k=e;m.notifySubscribers(k)}finally{a.q.end(),n=!1}v.length||x()}}function m(){if(0<arguments.length){if("function"===typeof r)r.apply(c,arguments);else throw Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");return this}l||e();a.q.bb(m);return k}function h(){return!l||0<v.length}var k,l=!1,n=!1,p=b;p&&"object"==typeof p?(d=p,p=d.read):(d=d||{},p||(p=d.read));if("function"!=typeof p)throw Error("Pass a function that returns the value of the ko.computed");
+z=a.a.f.get(b,f);if(!c){if(z)throw Error("You cannot apply bindings multiple times to the same element.");a.a.f.set(b,f,!0)}a.j(function(){var f=d&&d instanceof a.A?d:new a.A(a.a.c(d)),x=f.$data;!z&&h&&a.jb(b,f);if(p=("function"==typeof c?c(f,b):c)||a.M.instance.getBindings(b,f))0===n&&(n=1,a.a.w(p,function(c){var e=a.d[c];if(e&&8===b.nodeType&&!a.e.L[c])throw Error("The binding '"+c+"' cannot be used with virtual elements");if(e&&"function"==typeof e.init&&(e=(0,e.init)(b,k(c),l,x,f))&&e.controlsDescendantBindings){if(r!==
+q)throw Error("Multiple bindings ("+r+" and "+c+") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");r=c}}),n=2),2===n&&a.a.w(p,function(c){var e=a.d[c];e&&"function"==typeof e.update&&(0,e.update)(b,k(c),l,x,f)})},null,{$:b});return{Sb:r===q}}a.d={};a.A=function(b,c,d){c?(a.a.extend(this,c),this.$parentContext=c,this.$parent=c.$data,this.$parents=(c.$parents||[]).slice(0),this.$parents.unshift(this.$parent)):(this.$parents=
+[],this.$root=b,this.ko=a);this.$data=b;d&&(this[d]=b)};a.A.prototype.createChildContext=function(b,c){return new a.A(b,this,c)};a.A.prototype.extend=function(b){var c=a.a.extend(new a.A,this);return a.a.extend(c,b)};var f="__ko_boundElement";a.jb=function(b,c){if(2==arguments.length)a.a.f.set(b,"__ko_bindingContext__",c);else return a.a.f.get(b,"__ko_bindingContext__")};a.Ka=function(b,c,f){1===b.nodeType&&a.e.Za(b);return d(b,c,f,!0)};a.Ja=function(a,c){1!==c.nodeType&&8!==c.nodeType||b(a,c,!0)};
+a.Ia=function(a,b){if(b&&1!==b.nodeType&&8!==b.nodeType)throw Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");b=b||w.document.body;c(a,b,!0)};a.na=function(b){switch(b.nodeType){case 1:case 8:var c=a.jb(b);if(c)return c;if(b.parentNode)return a.na(b.parentNode)}return q};a.ub=function(b){return(b=a.na(b))?b.$data:q};a.b("bindingHandlers",a.d);a.b("applyBindings",a.Ia);a.b("applyBindingsToDescendants",a.Ja);a.b("applyBindingsToNode",a.Ka);
+a.b("contextFor",a.na);a.b("dataFor",a.ub)})();var K={"class":"className","for":"htmlFor"};a.d.attr={update:function(b,c){var d=a.a.c(c())||{};a.a.w(d,function(c,d){d=a.a.c(d);var e=!1===d||null===d||d===q;e&&b.removeAttribute(c);8>=a.a.ca&&c in K?(c=K[c],e?b.removeAttribute(c):b[c]=d):e||b.setAttribute(c,d.toString());"name"===c&&a.a.gb(b,e?"":d.toString())})}};a.d.checked={init:function(b,c,d){a.a.o(b,"click",function(){var f;if("checkbox"==b.type)f=b.checked;else if("radio"==b.type&&b.checked)f=
+h.optionsValue,b),a.h.W(c,a.a.c(d)),b=f(b,h.optionsText,d),a.a.ib(c,b));return[c]},null,d);p=null;e&&"value"in h&&J(b,a.a.ya(h.value),!0);a.a.zb(b);m&&20<Math.abs(m-b.scrollTop)&&(b.scrollTop=m)}};a.d.options.wa="__ko.optionValueDomData__";a.d.selectedOptions={init:function(b,c,d){a.a.o(b,"change",function(){var f=c(),g=[];a.a.p(b.getElementsByTagName("option"),function(b){b.selected&&g.push(a.h.n(b))});a.g.ha(f,d,"selectedOptions",g)})},update:function(b,c){if("select"!=a.a.u(b))throw Error("values binding applies only to SELECT elements");
+var d=a.a.c(c());d&&"number"==typeof d.length&&a.a.p(b.getElementsByTagName("option"),function(b){var c=0<=a.a.k(d,a.h.n(b));a.a.hb(b,c)})}};a.d.style={update:function(b,c){var d=a.a.c(c()||{});a.a.w(d,function(c,d){d=a.a.c(d);b.style[c]=d||""})}};a.d.submit={init:function(b,c,d,f){if("function"!=typeof c())throw Error("The value for a submit binding must be a function");a.a.o(b,"submit",function(a){var d,m=c();try{d=m.call(f,b)}finally{!0!==d&&(a.preventDefault?a.preventDefault():a.returnValue=!1)}})}};
+{init:function(c,d,f,g){return a.d.event.init.call(this,c,function(){var a={};a[b]=d();return a},f,g)}}})("click");a.v=function(){};a.v.prototype.renderTemplateSource=function(){throw Error("Override renderTemplateSource");};a.v.prototype.createJavaScriptEvaluatorBlock=function(){throw Error("Override createJavaScriptEvaluatorBlock");};a.v.prototype.makeTemplateSource=function(b,c){if("string"==typeof b){c=c||s;var d=c.getElementById(b);if(!d)throw Error("Cannot find template with ID "+b);return new a.l.i(d)}if(1==
+b.nodeType||8==b.nodeType)return new a.l.Q(b);throw Error("Unknown template type: "+b);};a.v.prototype.renderTemplate=function(a,c,d,f){a=this.makeTemplateSource(a,f);return this.renderTemplateSource(a,c,d)};a.v.prototype.isTemplateRewritten=function(a,c){return!1===this.allowTemplateRewriting?!0:this.makeTemplateSource(a,c).data("isRewritten")};a.v.prototype.rewriteTemplate=function(a,c,d){a=this.makeTemplateSource(a,d);c=c(a.text());a.text(c);a.data("isRewritten",!0)};a.b("templateEngine",a.v);
+a.Ea=function(){function b(b,c,d,m){b=a.g.da(b);for(var h=a.g.S,k=0;k<b.length;k++){var l=b[k].key;if(h.hasOwnProperty(l)){var n=h[l];if("function"===typeof n){if(l=n(b[k].value))throw Error(l);}else if(!n)throw Error("This template engine does not support the '"+l+"' binding within its templates");}}d="ko.__tr_ambtns(function($context,$element){return(function(){return{ "+a.g.ea(b)+" } })()},'"+d.toLowerCase()+"')";return m.createJavaScriptEvaluatorBlock(d)+c}var c=/(<([a-z]+\d*)(?:\s+(?!data-bind\s*=\s*)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi,
+d=/\x3c!--\s*ko\b\s*([\s\S]*?)\s*--\x3e/g;return{Ab:function(b,c,d){c.isTemplateRewritten(b,d)||c.rewriteTemplate(b,function(b){return a.Ea.Lb(b,c)},d)},Lb:function(a,g){return a.replace(c,function(a,c,d,f,l){return b(l,c,d,g)}).replace(d,function(a,c){return b(c,"\x3c!-- ko --\x3e","#comment",g)})},qb:function(b,c){return a.s.va(function(d,m){var h=d.nextSibling;h&&h.nodeName.toLowerCase()===c&&a.Ka(h,b,m)})}}}();a.b("__tr_ambtns",a.Ea.qb);(function(){a.l={};a.l.i=function(a){this.i=a};a.l.i.prototype.text=
+f.nodeType&&8!==f.nodeType||d(f)}function c(c,d){if(c.length){var f=c[0],g=c[c.length-1];b(f,g,function(b){a.Ia(d,b)});b(f,g,function(b){a.s.nb(b,[d])})}}function d(a){return a.nodeType?a:0<a.length?a[0]:null}function f(b,f,h,k,l){l=l||{};var n=b&&d(b),n=n&&n.ownerDocument,p=l.templateEngine||g;a.Ea.Ab(h,p,n);h=p.renderTemplate(h,k,l,n);if("number"!=typeof h.length||0<h.length&&"number"!=typeof h[0].nodeType)throw Error("Template engine must return an array of DOM nodes");n=!1;switch(f){case "replaceChildren":a.e.P(b,
+h);n=!0;break;case "replaceNode":a.a.eb(b,h);n=!0;break;case "ignoreTargetNode":break;default:throw Error("Unknown renderMode: "+f);}n&&(c(h,k),l.afterRender&&a.q.I(l.afterRender,null,[h,k.$data]));return h}var g;a.Ba=function(b){if(b!=q&&!(b instanceof a.v))throw Error("templateEngine must inherit from ko.templateEngine");g=b};a.za=function(b,c,h,k,l){h=h||{};if((h.templateEngine||g)==q)throw Error("Set a template engine before calling renderTemplate");l=l||"replaceChildren";if(k){var n=d(k);return a.j(function(){var g=
+c,c=a.a.c(d.name),"if"in d&&(f=a.a.c(d["if"])),f&&"ifnot"in d&&(f=!a.a.c(d.ifnot)),n=a.a.c(d.data));"foreach"in d?p=a.Rb(c||b,f&&d.foreach||[],d,b,g):f?(g="data"in d?g.createChildContext(n,d.as):g,p=a.za(c||b,g,d,b)):a.e.ba(b);g=p;(n=a.a.f.get(b,"__ko__templateComputedDomDataKey__"))&&"function"==typeof n.B&&n.B();a.a.f.set(b,"__ko__templateComputedDomDataKey__",g&&g.ta()?g:q)}};a.g.S.template=function(b){b=a.g.da(b);return 1==b.length&&b[0].unknown||a.g.Jb(b,"name")?null:"This template engine does not support anonymous templates nested within its templates"};
+function(b,f,g){g=g||{};if(2>a)throw Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");var e=b.data("precompiled");e||(e=b.text()||"",e=t.template(null,"{{ko_with $item.koBindingContext}}"+e+"{{/ko_with}}"),b.data("precompiled",e));b=[f.$data];f=t.extend({koBindingContext:f},g.templateOptions);f=t.tmpl(e,b,f);f.appendTo(s.createElement("div"));t.fragments={};return f};this.createJavaScriptEvaluatorBlock=function(a){return"{{ko_code ((function() { return "+
+ <p class="muted credit">Visit the <a href="http://spring.io/spring-security">Spring Security</a> site for more <a href="https://github.com/spring-projects/spring-security/blob/master/samples/">samples</a>.</p>