org.cometd.bayeux.server.Authorizer Maven / Gradle / Ivy
/*
* Copyright (c) 2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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.
*/
package org.cometd.bayeux.server;
import org.cometd.bayeux.Channel;
import org.cometd.bayeux.ChannelId;
import org.cometd.bayeux.Promise;
/**
* {@link Authorizer}s authorize {@link Operation operations} on {@link ServerChannel channels}.
* Authorizers can be {@link ConfigurableServerChannel#addAuthorizer(Authorizer) added to} and
* {@link ConfigurableServerChannel#removeAuthorizer(Authorizer)} removed from} channels, even wildcard
* channels.
* {@link Authorizer}s work together with the {@link SecurityPolicy} to determine if a
* {@link Operation#CREATE channel creation}, a {@link Operation#SUBSCRIBE channel subscribe} or a
* {@link Operation#PUBLISH publish operation} may succeed.
*
* For an operation on a channel, the authorizers on the wildcard channels that match the channel and the
* authorizers on the channel itself (together known at the authorizers set for that channel) will be
* consulted to check if the the operation is granted, denied or ignored.
* The list of wildcard channels that match the channel is obtained from {@link ChannelId#getWildIds()}.
* The following is the authorization algorithm:
*
* - If there is a security policy, and the security policy denies the request, then the request is denied.
* - Otherwise, if the authorizers set is empty, the request is granted.
* - Otherwise, if no authorizer explicitly grants the operation, the request is denied.
* - Otherwise, if at least one authorizer explicitly grants the operation, and no authorizer explicitly denies the
* operation, the request is granted.
* - Otherwise, if one authorizer explicitly denies the operation, remaining authorizers are not consulted, and the
* request is denied.
*
* The order in which the authorizers are checked is not important.
* Typically, authorizers are setup during the configuration of a channel:
*
* BayeuxServer bayeuxServer = ...;
* bayeuxServer.createIfAbsent("/television/cnn", new ConfigurableServerChannel.Initializer()
* {
* public void configureChannel(ConfigurableServerChannel channel)
* {
* // Grant subscribe to all
* channel.addAuthorizer(GrantAuthorizer.GRANT_SUBSCRIBE);
*
* // Grant publishes only to CNN employees
* channel.addAuthorizer(new Authorizer()
* {
* public Result authorize(Operation operation, ChannelId channel,
* ServerSession session, ServerMessage message)
* {
* if (operation == Operation.PUBLISH &&
* session.getAttribute("isCNNEmployee") == Boolean.TRUE)
* return Result.grant();
* else
* return Result.ignore();
* }
* });
* }
* });
*
* A typical usage of authorizers is as follows:
*
* - Create a wildcard authorizer that matches all channels and neither grants or
* denies (e.g. use {@code org.cometd.server.authorizer.GrantAuthorizer.GRANT_NONE}).
* This authorizer can be added to channel /** or to a more specific channel for your application such as
* /game/**.
* This ensures that authorizers set is not empty and that another authorizer must explicitly grant access.
* - For public channels, that all users can access, add authorizers that will simply grant
* publish and/or subscribe permissions to the specific or wildcard channels.
* - For access controlled channels (e.g. only nominated players can publish to a game channel), then
* specific implementation of authorizers need to be created that will check identities and possibly other
* state before granting permission.
* Typically there is no need for such authorizers to explicitly deny access, unless that attempted access
* represents a specific error condition that needs to be passed to the client in the message associated
* with a deny.
* - For cross cutting concerns, such as checking a users credit or implementing user bans, authorizers
* can be created to explicitly deny access, without the need to modify all authorizers already in place
* that may grant.
*
*
* @see SecurityPolicy
*/
public interface Authorizer {
/**
* Operations that are to be authorized on a channel
*/
enum Operation {
/**
* The operation to create a channel that does not exist
*/
CREATE,
/**
* The operation to subscribe to a channel to receive messages published to it
*/
SUBSCRIBE,
/**
* The operation to publish messages to a channel
*/
PUBLISH
}
/**
* Callback invoked to authorize the given {@code operation} on the given {@code channel}.
* Additional parameters are passed to this method as context parameters, so that it is possible
* to implement complex logic based on the {@link ServerSession} and {@link ServerMessage} that
* are requesting the authorization.
* Note that the message channel is not the same as the {@code channelId} parameter. For example,
* for subscription requests, the message channel is {@link Channel#META_SUBSCRIBE}, while the
* {@code channelId} parameter is the channel for which the subscription is requested.
* Note that for {@link Operation#CREATE create operation}, the channel instance does not yet
* exist: it will be created only after the authorization is granted.
*
* @param operation the operation to authorize
* @param channel the channel for which the authorization has been requested
* @param session the session that is requesting the authorization
* @param message the message that triggered the authorization request
* @param promise the promise to notify of the authorization result
*/
default void authorize(Operation operation, ChannelId channel, ServerSession session, ServerMessage message, Promise promise) {
promise.succeed(authorize(operation, channel, session, message));
}
/**
* Blocking version of {@link #authorize(Operation, ChannelId, ServerSession, ServerMessage, Promise)}.
*
* @param operation the operation to authorize
* @param channel the channel for which the authorization has been requested
* @param session the session that is requesting the authorization
* @param message the message that triggered the authorization request
* @return the authorization result
*/
Result authorize(Operation operation, ChannelId channel, ServerSession session, ServerMessage message);
/**
* The result of an authentication request.
*/
public static abstract class Result {
/**
* @param reason the reason for which the authorization is denied
* @return a result that denies the authorization
*/
public static Result deny(String reason) {
return new Denied(reason);
}
/**
* @return a result that grants the authorization
*/
public static Result grant() {
return Granted.GRANTED;
}
/**
* @return a result that ignores the authorization, leaving the decision to other {@link Authorizer}s.
*/
public static Result ignore() {
return Ignored.IGNORED;
}
public boolean isDenied() {
return false;
}
public boolean isGranted() {
return false;
}
@Override
public String toString() {
return getClass().getSimpleName().toLowerCase();
}
public static final class Denied extends Result {
private final String reason;
private Denied(String reason) {
if (reason == null) {
reason = "";
}
this.reason = reason;
}
public String getReason() {
return reason;
}
@Override
public boolean isDenied() {
return true;
}
@Override
public String toString() {
return super.toString() + " (reason='" + reason + "')";
}
}
public static final class Granted extends Result {
private static final Granted GRANTED = new Granted();
private Granted() {
}
@Override
public boolean isGranted() {
return true;
}
}
public static final class Ignored extends Result {
private static final Ignored IGNORED = new Ignored();
private Ignored() {
}
}
}
}