001/*
002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Nelson Silva <nelson.silva@inevo.pt>
018 */
019package org.nuxeo.ecm.platform.auth.saml.slo;
020
021import org.joda.time.DateTime;
022import org.nuxeo.ecm.platform.auth.saml.AbstractSAMLProfile;
023import org.nuxeo.ecm.platform.auth.saml.SAMLConfiguration;
024import org.nuxeo.ecm.platform.auth.saml.SAMLCredential;
025import org.opensaml.common.SAMLException;
026import org.opensaml.common.SAMLObject;
027import org.opensaml.common.SAMLVersion;
028import org.opensaml.common.binding.SAMLMessageContext;
029import org.opensaml.saml2.core.*;
030import org.opensaml.saml2.metadata.SingleLogoutService;
031import org.opensaml.xml.encryption.DecryptionException;
032
033/**
034 * WebSLO (Single Log Out) profile implementation.
035 *
036 * @since 6.0
037 */
038public class SLOProfileImpl extends AbstractSAMLProfile implements SLOProfile {
039
040    public SLOProfileImpl(SingleLogoutService slo) {
041        super(slo);
042    }
043
044    @Override
045    public String getProfileIdentifier() {
046        return PROFILE_URI;
047    }
048
049    public LogoutRequest buildLogoutRequest(SAMLMessageContext context, SAMLCredential credential) throws SAMLException {
050
051        LogoutRequest request = build(LogoutRequest.DEFAULT_ELEMENT_NAME);
052        request.setID(newUUID());
053        request.setVersion(SAMLVersion.VERSION_20);
054        request.setIssueInstant(new DateTime());
055        request.setDestination(getEndpoint().getLocation());
056
057        Issuer issuer = build(Issuer.DEFAULT_ELEMENT_NAME);
058        issuer.setValue(SAMLConfiguration.getEntityId());
059        request.setIssuer(issuer);
060
061        // Add session indexes
062        if (credential.getSessionIndexes() == null || credential.getSessionIndexes().isEmpty()) {
063            throw new SAMLException("No session indexes found");
064        }
065        for (String sessionIndex : credential.getSessionIndexes()) {
066            SessionIndex index = build(SessionIndex.DEFAULT_ELEMENT_NAME);
067            index.setSessionIndex(sessionIndex);
068            request.getSessionIndexes().add(index);
069        }
070
071        request.setNameID(credential.getNameID());
072
073        return request;
074
075    }
076
077    public boolean processLogoutRequest(SAMLMessageContext context, SAMLCredential credential) throws SAMLException {
078
079        SAMLObject message = context.getInboundSAMLMessage();
080
081        // Verify type
082        if (message == null || !(message instanceof LogoutRequest)) {
083            throw new SAMLException("Message is not of a LogoutRequest object type");
084        }
085
086        LogoutRequest request = (LogoutRequest) message;
087
088        // Validate signature of the response if present
089        if (request.getSignature() != null) {
090            log.debug("Verifying message signature");
091            validateSignature(request.getSignature(), context.getPeerEntityId());
092            context.setInboundSAMLMessageAuthenticated(true);
093        }
094
095        // TODO - Validate destination
096
097        // Validate issuer
098        if (request.getIssuer() != null) {
099            log.debug("Verifying issuer of the message");
100            Issuer issuer = request.getIssuer();
101            validateIssuer(issuer, context);
102        }
103
104        // TODO - Validate issue time
105
106        // Get and validate the NameID
107        NameID nameID;
108        if (getDecrypter() != null && request.getEncryptedID() != null) {
109            try {
110                nameID = (NameID) getDecrypter().decrypt(request.getEncryptedID());
111            } catch (DecryptionException e) {
112                throw new SAMLException("Failed to decrypt NameID", e);
113            }
114        } else {
115            nameID = request.getNameID();
116        }
117
118        if (nameID == null) {
119            throw new SAMLException("The requested NameID is invalid");
120        }
121
122        // If no index is specified do logout
123        if (request.getSessionIndexes() == null || request.getSessionIndexes().isEmpty()) {
124            return true;
125        }
126
127        // Else check if this is on of our session indexes
128        for (SessionIndex sessionIndex : request.getSessionIndexes()) {
129            if (credential.getSessionIndexes().contains(sessionIndex.getSessionIndex())) {
130                return true;
131            }
132        }
133
134        return false;
135    }
136
137    public void processLogoutResponse(SAMLMessageContext context) throws SAMLException {
138
139        SAMLObject message = context.getInboundSAMLMessage();
140
141        if (!(message instanceof LogoutResponse)) {
142            throw new SAMLException("Message is not of a LogoutResponse object type");
143        }
144        LogoutResponse response = (LogoutResponse) message;
145
146        // Validate signature of the response if present
147        if (response.getSignature() != null) {
148            log.debug("Verifying message signature");
149            validateSignature(response.getSignature(), context.getPeerEntityId());
150            context.setInboundSAMLMessageAuthenticated(true);
151        }
152
153        // TODO - Validate destination
154
155        // Validate issuer
156        if (response.getIssuer() != null) {
157            log.debug("Verifying issuer of the message");
158            Issuer issuer = response.getIssuer();
159            validateIssuer(issuer, context);
160        }
161
162        // TODO - Validate issue time
163
164        // Verify status
165        String statusCode = response.getStatus().getStatusCode().getValue();
166        if (!statusCode.equals(StatusCode.SUCCESS_URI) && !statusCode.equals(StatusCode.PARTIAL_LOGOUT_URI)) {
167            log.warn("Invalid status code " + statusCode + ": " + response.getStatus().getStatusMessage());
168        }
169    }
170}