001: /*
002: * Copyright 2005-2007 Noelios Consulting.
003: *
004: * The contents of this file are subject to the terms of the Common Development
005: * and Distribution License (the "License"). You may not use this file except in
006: * compliance with the License.
007: *
008: * You can obtain a copy of the license at
009: * http://www.opensource.org/licenses/cddl1.txt See the License for the specific
010: * language governing permissions and limitations under the License.
011: *
012: * When distributing Covered Code, include this CDDL HEADER in each file and
013: * include the License file at http://www.opensource.org/licenses/cddl1.txt If
014: * applicable, add the following below this CDDL HEADER, with the fields
015: * enclosed by brackets "[]" replaced with your own identifying information:
016: * Portions Copyright [yyyy] [name of copyright owner]
017: */
018:
019: package org.restlet;
020:
021: import java.util.Map;
022: import java.util.concurrent.ConcurrentHashMap;
023:
024: import org.restlet.data.ChallengeRequest;
025: import org.restlet.data.ChallengeResponse;
026: import org.restlet.data.ChallengeScheme;
027: import org.restlet.data.Request;
028: import org.restlet.data.Response;
029: import org.restlet.data.Status;
030:
031: /**
032: * Filter guarding the access to an attached Restlet.
033: *
034: * @see <a href="http://www.restlet.org/documentation/1.0/tutorial#part09">Tutorial: Guarding
035: * access to sensitive resources</a>
036: * @author Jerome Louvel (contact@noelios.com)
037: */
038: public class Guard extends Filter {
039: /** Map of secrets (login/password combinations). */
040: private final Map<String, char[]> secrets;
041:
042: /** The authentication scheme. */
043: private ChallengeScheme scheme;
044:
045: /** The authentication realm. */
046: private String realm;
047:
048: /**
049: * Constructor.
050: *
051: * @param context
052: * The context.
053: * @param scheme
054: * The authentication scheme to use.
055: * @param realm
056: * The authentication realm.
057: */
058: public Guard(Context context, ChallengeScheme scheme, String realm) {
059: super (context);
060: this .secrets = new ConcurrentHashMap<String, char[]>();
061:
062: if ((scheme == null)) {
063: throw new IllegalArgumentException(
064: "Please specify an authentication scheme. Use the 'None' challenge if no authentication is required.");
065: } else {
066: this .scheme = scheme;
067: this .realm = realm;
068: }
069: }
070:
071: /**
072: * Accepts the call. By default, it is invoked it the request is
073: * authenticated and authorized, and asks the attached Restlet to handle the
074: * call.
075: *
076: * @param request
077: * The request to accept.
078: * @param response
079: * The response to accept.
080: */
081: public void accept(Request request, Response response) {
082: // Invoke the attached Restlet
083: super .doHandle(request, response);
084: }
085:
086: /**
087: * Indicates if the call is properly authenticated. By default, this
088: * delegates credential checking to checkSecret().
089: *
090: * @param request
091: * The request to authenticate.
092: * @return -1 if the given credentials were invalid, 0 if no credentials
093: * were found and 1 otherwise.
094: * @see #checkSecret(String, char[])
095: */
096: public int authenticate(Request request) {
097: int result = 0;
098:
099: if (this .scheme != null) {
100: // An authentication scheme has been defined,
101: // the request must be authenticated
102: ChallengeResponse cr = request.getChallengeResponse();
103:
104: if (cr != null) {
105: if (this .scheme.equals(cr.getScheme())) {
106: // The challenge schemes are compatible
107: String identifier = request.getChallengeResponse()
108: .getIdentifier();
109: char[] secret = request.getChallengeResponse()
110: .getSecret();
111:
112: // Check the credentials
113: if ((identifier != null) && (secret != null)) {
114: result = checkSecret(identifier, secret) ? 1
115: : -1;
116: }
117: } else {
118: // The challenge schemes are incompatible, we need to
119: // challenge the client
120: }
121: } else {
122: // No challenge response found, we need to challenge the client
123: }
124: }
125:
126: return result;
127: }
128:
129: /**
130: * Indicates if the secret is valid for the given identifier. By default,
131: * this returns true given the correct login/password couple as verified via
132: * the findSecret() method.
133: *
134: * @param identifier
135: * the identifier
136: * @param secret
137: * the identifier's secret
138: * @return true if the secret is valid for the given identifier
139: */
140: protected boolean checkSecret(String identifier, char[] secret) {
141: boolean result = false;
142: char[] secret2 = findSecret(identifier);
143: if (secret == null || secret2 == null) {
144: // check if both are null
145: result = (secret == secret2);
146: } else {
147: if (secret.length == secret2.length) {
148: boolean equals = true;
149: for (int i = 0; i < secret.length && equals; i++) {
150: equals = (secret[i] == secret2[i]);
151: }
152: result = equals;
153: }
154: }
155:
156: return result;
157: }
158:
159: /**
160: * Indicates if the request is authorized to pass through the Guard. This
161: * method is only called if the call was sucessfully authenticated. It
162: * always returns true by default. If specific checks are required, they
163: * could be added by overriding this method.
164: *
165: * @param request
166: * The request to authorize.
167: * @return True if the request is authorized.
168: */
169: public boolean authorize(Request request) {
170: return true;
171: }
172:
173: /**
174: * Challenges the client by adding a challenge request to the response and
175: * by setting the status to CLIENT_ERROR_UNAUTHORIZED.
176: *
177: * @param response
178: * The response to update.
179: */
180: public void challenge(Response response) {
181: response.setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
182: response.setChallengeRequest(new ChallengeRequest(this .scheme,
183: this .realm));
184: }
185:
186: /**
187: * Handles the call by distributing it to the next Restlet.
188: *
189: * @param request
190: * The request to handle.
191: * @param response
192: * The response to update.
193: */
194: public void doHandle(Request request, Response response) {
195: switch (authenticate(request)) {
196: case 1:
197: // Valid credentials provided
198: if (authorize(request)) {
199: accept(request, response);
200: } else {
201: forbid(response);
202: }
203: break;
204: case 0:
205: // No credentials provided
206: challenge(response);
207: break;
208: case -1:
209: // Wrong credentials provided
210: forbid(response);
211: break;
212: }
213: }
214:
215: /**
216: * Finds the secret associated to a given identifier. By default it looks up
217: * into the secrets map, but this behavior can be overriden.
218: *
219: * @param identifier
220: * The identifier to lookup.
221: * @return The secret associated to the identifier or null.
222: */
223: protected char[] findSecret(String identifier) {
224: return getSecrets().get(identifier);
225: }
226:
227: /**
228: * Rejects the call due to a failed authentication or authorization. This
229: * can be overriden to change the defaut behavior, for example to display an
230: * error page. By default, if authentication is required, the challenge
231: * method is invoked, otherwise the call status is set to
232: * CLIENT_ERROR_FORBIDDEN.
233: *
234: * @param response
235: * The reject response.
236: */
237: public void forbid(Response response) {
238: response.setStatus(Status.CLIENT_ERROR_FORBIDDEN);
239: }
240:
241: /**
242: * Returns the map of identifiers and secrets.
243: *
244: * @return The map of identifiers and secrets.
245: */
246: public Map<String, char[]> getSecrets() {
247: return this.secrets;
248: }
249:
250: }
|