001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 *
019 * Contributors:
020 *     Arnaud Kervern
021 */
022package org.nuxeo.ecm.core.opencmis.bindings;
023
024import java.util.ArrayList;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030
031import javax.xml.bind.JAXBContext;
032import javax.xml.bind.JAXBElement;
033import javax.xml.bind.JAXBException;
034import javax.xml.bind.annotation.XmlAccessType;
035import javax.xml.bind.annotation.XmlAccessorType;
036import javax.xml.bind.annotation.XmlAnyAttribute;
037import javax.xml.bind.annotation.XmlAnyElement;
038import javax.xml.bind.annotation.XmlAttribute;
039import javax.xml.bind.annotation.XmlElement;
040import javax.xml.bind.annotation.XmlElementDecl;
041import javax.xml.bind.annotation.XmlID;
042import javax.xml.bind.annotation.XmlRegistry;
043import javax.xml.bind.annotation.XmlSchemaType;
044import javax.xml.bind.annotation.XmlSeeAlso;
045import javax.xml.bind.annotation.XmlType;
046import javax.xml.bind.annotation.XmlValue;
047import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
048import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
049import javax.xml.namespace.QName;
050import javax.xml.ws.handler.MessageContext;
051import javax.xml.ws.handler.soap.SOAPHandler;
052import javax.xml.ws.handler.soap.SOAPMessageContext;
053
054import org.apache.chemistry.opencmis.commons.server.CallContext;
055import org.apache.chemistry.opencmis.server.impl.webservices.AbstractService;
056
057/**
058 * Extracts username and password from a UsernameToken
059 *
060 * @since 5.7.3
061 */
062public class CXFAuthHandler implements SOAPHandler<SOAPMessageContext> {
063
064    protected static final JAXBContext WSSE_CONTEXT;
065
066    static {
067        try {
068            WSSE_CONTEXT = JAXBContext.newInstance(ObjectFactory.class);
069        } catch (JAXBException e) {
070            throw new ExceptionInInitializerError(e);
071        }
072    }
073
074    protected static final String WSSE_NS = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
075
076    protected static final QName WSSE_SECURITY = new QName(WSSE_NS, "Security");
077
078    protected static final QName WSSE_USERNAME_TOKEN = new QName(WSSE_NS, "UsernameToken");
079
080    protected static final QName WSSE_PASSWORD = new QName(WSSE_NS, "Password");
081
082    protected static final Set<QName> HEADERS = new HashSet<QName>();
083
084    static {
085        HEADERS.add(WSSE_SECURITY);
086    }
087
088    @Override
089    public Set<QName> getHeaders() {
090        return HEADERS;
091    }
092
093    @Override
094    public void close(MessageContext context) {
095    }
096
097    @Override
098    public boolean handleFault(SOAPMessageContext context) {
099        return true;
100    }
101
102    @Override
103    @SuppressWarnings("unchecked")
104    public boolean handleMessage(SOAPMessageContext context) {
105        if ((Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY)) {
106            // we are only looking at inbound messages
107            return true;
108        }
109
110        Map<String, String> callContextMap = null;
111
112        Object[] secHeaders = context.getHeaders(WSSE_SECURITY, WSSE_CONTEXT, true);
113        if (secHeaders != null && secHeaders.length > 0) {
114            for (Object header : secHeaders) {
115                if (!(header instanceof JAXBElement)) {
116                    continue;
117                }
118
119                if (!(((JAXBElement<?>) header).getValue() instanceof SecurityHeaderType)) {
120                    continue;
121                }
122
123                callContextMap = extractUsernamePassword((JAXBElement<SecurityHeaderType>) header);
124                if (callContextMap != null) {
125                    break;
126                }
127            }
128        }
129
130        // add user and password to context
131        if (callContextMap == null) {
132            callContextMap = new HashMap<String, String>();
133        }
134
135        context.put(AbstractService.CALL_CONTEXT_MAP, callContextMap);
136        context.setScope(AbstractService.CALL_CONTEXT_MAP, MessageContext.Scope.APPLICATION);
137
138        return true;
139    }
140
141    @SuppressWarnings("unchecked")
142    protected Map<String, String> extractUsernamePassword(JAXBElement<SecurityHeaderType> sht) {
143        String username = null;
144        String password = null;
145        for (Object uno : sht.getValue().getAny()) {
146            if ((uno instanceof JAXBElement) && ((JAXBElement<?>) uno).getValue() instanceof UsernameTokenType) {
147                UsernameTokenType utt = ((JAXBElement<UsernameTokenType>) uno).getValue();
148                username = utt.getUsername().getValue();
149
150                for (Object po : utt.getAny()) {
151                    if ((po instanceof JAXBElement) && ((JAXBElement<?>) po).getValue() instanceof PasswordString) {
152                        password = ((JAXBElement<PasswordString>) po).getValue().getValue();
153                        break;
154                    }
155                }
156
157                break;
158            }
159        }
160        Map<String, String> result = null;
161        if (username != null) {
162            result = new HashMap<String, String>();
163            result.put(CallContext.USERNAME, username);
164            result.put(CallContext.PASSWORD, password);
165        }
166        return result;
167    }
168
169    // --- JAXB classes ---
170
171    @XmlRegistry
172    public static class ObjectFactory {
173
174        public ObjectFactory() {
175        }
176
177        public SecurityHeaderType createSecurityHeaderType() {
178            return new SecurityHeaderType();
179        }
180
181        public UsernameTokenType createUsernameTokenType() {
182            return new UsernameTokenType();
183        }
184
185        public PasswordString createPasswordString() {
186            return new PasswordString();
187        }
188
189        public AttributedString createAttributedString() {
190            return new AttributedString();
191        }
192
193        @XmlElementDecl(namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", name = "Security")
194        public JAXBElement<SecurityHeaderType> createSecurity(SecurityHeaderType value) {
195            return new JAXBElement<SecurityHeaderType>(WSSE_SECURITY, SecurityHeaderType.class, null, value);
196        }
197
198        @XmlElementDecl(namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", name = "UsernameToken")
199        public JAXBElement<UsernameTokenType> createUsernameToken(UsernameTokenType value) {
200            return new JAXBElement<UsernameTokenType>(WSSE_USERNAME_TOKEN, UsernameTokenType.class, null, value);
201        }
202
203        @XmlElementDecl(namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", name = "Password")
204        public JAXBElement<PasswordString> createPassword(PasswordString value) {
205            return new JAXBElement<PasswordString>(WSSE_PASSWORD, PasswordString.class, null, value);
206        }
207
208    }
209
210    @XmlAccessorType(XmlAccessType.FIELD)
211    @XmlType(name = "SecurityHeaderType", propOrder = { "any" })
212    public static class SecurityHeaderType {
213
214        @XmlAnyElement(lax = true)
215        protected List<Object> any;
216        @XmlAnyAttribute
217        private final Map<QName, String> otherAttributes = new HashMap<QName, String>();
218
219        public List<Object> getAny() {
220            if (any == null) {
221                any = new ArrayList<Object>();
222            }
223            return this.any;
224        }
225
226        public Map<QName, String> getOtherAttributes() {
227            return otherAttributes;
228        }
229
230    }
231
232    @XmlAccessorType(XmlAccessType.FIELD)
233    @XmlType(name = "UsernameTokenType", propOrder = { "username", "any" })
234    public static class UsernameTokenType {
235
236        @XmlElement(name = "Username", namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", required = true)
237        protected AttributedString username;
238        @XmlAnyElement(lax = true)
239        protected List<Object> any;
240        @XmlAttribute(name = "Id", namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd")
241        @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
242        @XmlID
243        @XmlSchemaType(name = "ID")
244        protected String id;
245        @XmlAnyAttribute
246        private final Map<QName, String> otherAttributes = new HashMap<QName, String>();
247
248        public AttributedString getUsername() {
249            return username;
250        }
251
252        public void setUsername(AttributedString value) {
253            this.username = value;
254        }
255
256        public List<Object> getAny() {
257            if (any == null) {
258                any = new ArrayList<Object>();
259            }
260            return this.any;
261        }
262
263        public String getId() {
264            return id;
265        }
266
267        public void setId(String value) {
268            this.id = value;
269        }
270
271        public Map<QName, String> getOtherAttributes() {
272            return otherAttributes;
273        }
274    }
275
276    @XmlAccessorType(XmlAccessType.FIELD)
277    @XmlType(name = "PasswordString")
278    public static class PasswordString extends AttributedString {
279
280        @XmlAttribute(name = "Type")
281        @XmlSchemaType(name = "anyURI")
282        protected String type;
283
284        public String getType() {
285            return type;
286        }
287
288        public void setType(String value) {
289            this.type = value;
290        }
291    }
292
293    @XmlAccessorType(XmlAccessType.FIELD)
294    @XmlType(name = "AttributedString", propOrder = { "value" })
295    @XmlSeeAlso({ PasswordString.class })
296    public static class AttributedString {
297
298        @XmlValue
299        protected String value;
300        @XmlAttribute(name = "Id", namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd")
301        @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
302        @XmlID
303        @XmlSchemaType(name = "ID")
304        protected String id;
305        @XmlAnyAttribute
306        private final Map<QName, String> otherAttributes = new HashMap<QName, String>();
307
308        public String getValue() {
309            return value;
310        }
311
312        public void setValue(String value) {
313            this.value = value;
314        }
315
316        public String getId() {
317            return id;
318        }
319
320        public void setId(String value) {
321            this.id = value;
322        }
323
324        public Map<QName, String> getOtherAttributes() {
325            return otherAttributes;
326        }
327    }
328
329}