001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.geronimo.security.deployment;
018    
019    import java.util.ArrayList;
020    import java.util.Collection;
021    import java.util.HashMap;
022    import java.util.HashSet;
023    import java.util.List;
024    import java.util.Map;
025    import java.util.Set;
026    
027    import javax.xml.namespace.QName;
028    
029    import org.apache.geronimo.common.DeploymentException;
030    import org.apache.geronimo.deployment.DeploymentContext;
031    import org.apache.geronimo.deployment.service.SingleGBeanBuilder;
032    import org.apache.geronimo.deployment.service.XmlAttributeBuilder;
033    import org.apache.geronimo.deployment.service.XmlReferenceBuilder;
034    import org.apache.geronimo.deployment.xbeans.PatternType;
035    import org.apache.geronimo.deployment.xbeans.XmlAttributeType;
036    import org.apache.geronimo.gbean.AbstractName;
037    import org.apache.geronimo.gbean.AbstractNameQuery;
038    import org.apache.geronimo.gbean.GBeanData;
039    import org.apache.geronimo.gbean.GBeanInfo;
040    import org.apache.geronimo.gbean.GBeanInfoBuilder;
041    import org.apache.geronimo.gbean.GReferenceInfo;
042    import org.apache.geronimo.gbean.ReferenceMap;
043    import org.apache.geronimo.gbean.ReferencePatterns;
044    import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory;
045    import org.apache.geronimo.kernel.GBeanAlreadyExistsException;
046    import org.apache.geronimo.kernel.Kernel;
047    import org.apache.geronimo.kernel.Naming;
048    import org.apache.geronimo.security.jaas.JaasLoginModuleUse;
049    import org.apache.geronimo.security.jaas.LoginModuleControlFlag;
050    import org.apache.geronimo.security.jaas.LoginModuleControlFlagEditor;
051    import org.apache.geronimo.security.jaas.LoginModuleGBean;
052    import org.apache.geronimo.xbeans.geronimo.loginconfig.GerAbstractLoginModuleType;
053    import org.apache.geronimo.xbeans.geronimo.loginconfig.GerLoginConfigDocument;
054    import org.apache.geronimo.xbeans.geronimo.loginconfig.GerLoginConfigType;
055    import org.apache.geronimo.xbeans.geronimo.loginconfig.GerLoginModuleRefType;
056    import org.apache.geronimo.xbeans.geronimo.loginconfig.GerLoginModuleType;
057    import org.apache.geronimo.xbeans.geronimo.loginconfig.GerOptionType;
058    import org.apache.xmlbeans.XmlCursor;
059    import org.apache.xmlbeans.XmlObject;
060    import org.apache.xmlbeans.XmlOptions;
061    
062    
063    /**
064     * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
065     */
066    public class LoginConfigBuilder implements XmlReferenceBuilder {
067        public static final String LOGIN_CONFIG_NAMESPACE = GerLoginConfigDocument.type.getDocumentElementName().getNamespaceURI();
068        private static final QName LOGIN_MODULE_QNAME = new QName(LOGIN_CONFIG_NAMESPACE, "login-module");
069        private static final QName SERVER_SIDE_QNAME = new QName(null, "server-side");
070    
071        private final Naming naming;
072        private final Map xmlAttributeBuilderMap;
073    
074        public LoginConfigBuilder(Kernel kernel, Collection xmlAttributeBuilderMap) {
075            this(kernel.getNaming(), xmlAttributeBuilderMap);
076        }
077    
078        public LoginConfigBuilder(Naming naming, Collection xmlAttributeBuilders) {
079            this.naming = naming;
080            if (xmlAttributeBuilders != null) {
081                ReferenceMap.Key key = new ReferenceMap.Key() {
082    
083                    public Object getKey(Object object) {
084                        return ((XmlAttributeBuilder) object).getNamespace();
085                    }
086                };
087                xmlAttributeBuilderMap = new ReferenceMap(xmlAttributeBuilders, new HashMap(), key);
088            } else {
089                xmlAttributeBuilderMap = new HashMap();
090            }
091        }
092    
093        public String getNamespace() {
094            return LOGIN_CONFIG_NAMESPACE;
095        }
096    
097        public ReferencePatterns getReferences(XmlObject xmlObject, DeploymentContext context, AbstractName parentName, ClassLoader classLoader) throws DeploymentException {
098            List<GBeanData> uses = new ArrayList<GBeanData>();
099            GerLoginConfigType loginConfig = (GerLoginConfigType) xmlObject.copy().changeType(GerLoginConfigType.type);
100            XmlCursor xmlCursor = loginConfig.newCursor();
101            xmlCursor.push();
102            try {
103                //munge xml
104                if (xmlCursor.toChild(LOGIN_MODULE_QNAME)) {
105                    do {
106                        xmlCursor.removeAttribute(SERVER_SIDE_QNAME);
107                    } while (xmlCursor.toNextSibling(LOGIN_MODULE_QNAME));
108                }
109                xmlCursor.pop();
110                //validate
111                XmlOptions xmlOptions = new XmlOptions();
112                xmlOptions.setLoadLineNumbers();
113                Collection errors = new ArrayList();
114                xmlOptions.setErrorListener(errors);
115                if (!loginConfig.validate(xmlOptions)) {
116                    throw new DeploymentException("Invalid login configuration:\n" + errors + "\nDescriptor: " + loginConfig.toString());
117                }
118                //find the login modules
119                Set<String> loginModuleNames = new HashSet<String>();
120                boolean atStart = true;
121                while ((atStart && xmlCursor.toFirstChild()) || (!atStart && xmlCursor.toNextSibling())) {
122                    atStart = false;
123                    XmlObject child = xmlCursor.getObject();
124                    GerAbstractLoginModuleType abstractLoginModule = (GerAbstractLoginModuleType) child;
125                    String controlFlag = abstractLoginModule.getControlFlag().toString();
126                    boolean wrapPrincipals = (abstractLoginModule.isSetWrapPrincipals() && abstractLoginModule.getWrapPrincipals());
127                    ReferencePatterns loginModuleReferencePatterns;
128                    String name;
129                    if (abstractLoginModule instanceof GerLoginModuleRefType) {
130                        GerLoginModuleRefType loginModuleRef = (GerLoginModuleRefType) abstractLoginModule;
131                        PatternType patternType = loginModuleRef.getPattern();
132                        AbstractNameQuery loginModuleNameQuery = SingleGBeanBuilder.buildAbstractNameQuery(patternType, USE_REFERENCE_INFO);
133                        loginModuleReferencePatterns = new ReferencePatterns(loginModuleNameQuery);
134                        name = (String) loginModuleNameQuery.getName().get("name");
135                        if (name == null) {
136                            throw new DeploymentException("You must specify the name of the login module in the login module ref " + patternType);
137                        }
138    //TODO configid reinstate this check for duplicate domain names
139    //                    try
140    //                    {
141    //                        String loginDomain = (String) context.getAttribute(loginModuleName, "loginDomainName");
142    //                        if (!loginModuleNames.add(loginDomain))
143    //                        {
144    //                            throw new DeploymentException("Security realm contains two login domains called '" + loginDomain + "'");
145    //                        }
146    //                    }
147    //                    catch (DeploymentException e)
148    //                    {
149    //                        throw e;
150    //                    }
151    //                    catch (Exception e)
152    //                    {
153    //                        throw new DeploymentException("Unable to create reference to login module " + name, e);
154    //                    }
155                    } else if (abstractLoginModule instanceof GerLoginModuleType) {
156                        //create the LoginModuleGBean also
157                        AbstractName loginModuleName;
158    
159                        GerLoginModuleType loginModule = (GerLoginModuleType) abstractLoginModule;
160                        name = trim(loginModule.getLoginDomainName());
161                        if (!loginModuleNames.add(name)) {
162                            throw new DeploymentException("Security realm contains two login domains called '" + name + "'");
163                        }
164                        String className = trim(loginModule.getLoginModuleClass());
165                        Map<String, Object> options = new HashMap<String, Object>();
166                        GerOptionType[] optionArray = loginModule.getOptionArray();
167                        for (GerOptionType gerOptionType : optionArray) {
168                            String key = gerOptionType.getName();
169                            String value = trim(gerOptionType.getStringValue());
170                            options.put(key, value);
171                        }
172                        XmlAttributeType[] xmlOptionArray = loginModule.getXmlOptionArray();
173                        if (xmlOptionArray != null) {
174                            for (XmlAttributeType xmlOptionType : xmlOptionArray) {
175                                String key = xmlOptionType.getName().trim();
176                                XmlObject[] anys = xmlOptionType.selectChildren(XmlAttributeType.type.qnameSetForWildcardElements());
177                                if (anys.length != 1) {
178                                    throw new DeploymentException("Unexpected count of xs:any elements in xml-attribute " + anys.length + " qnameset: " + XmlAttributeType.type.qnameSetForWildcardElements());
179                                }
180                                String namespace = xmlObject.getDomNode().getNamespaceURI();
181                                XmlAttributeBuilder builder = (XmlAttributeBuilder) xmlAttributeBuilderMap.get(namespace);
182                                if (builder == null) {
183                                    throw new DeploymentException("No attribute builder deployed for namespace: " + namespace);
184                                }
185                                Object value = builder.getValue(xmlObject, null, classLoader);
186                                options.put(key, value);
187                            }
188                        }
189                        loginModuleName = naming.createChildName(parentName, name, NameFactory.LOGIN_MODULE);
190                        loginModuleReferencePatterns = new ReferencePatterns(loginModuleName);
191                        GBeanData loginModuleGBeanData = new GBeanData(loginModuleName, LoginModuleGBean.GBEAN_INFO);
192                        loginModuleGBeanData.setAttribute("loginDomainName", name);
193                        loginModuleGBeanData.setAttribute("loginModuleClass", className);
194                        loginModuleGBeanData.setAttribute("options", options);
195                        loginModuleGBeanData.setAttribute("wrapPrincipals", wrapPrincipals);
196    
197                        context.addGBean(loginModuleGBeanData);
198                    } else {
199                        throw new DeploymentException("Unknown abstract login module type: " + abstractLoginModule.getClass());
200                    }
201                    AbstractName thisName;
202                    thisName = naming.createChildName(parentName, name, "LoginModuleUse");
203                    GBeanData loginModuleUseGBeanData = new GBeanData(thisName, JaasLoginModuleUse.GBEAN_INFO);
204                    loginModuleUseGBeanData.setAttribute("controlFlag", getControlFlag(controlFlag));
205                    loginModuleUseGBeanData.setReferencePatterns("LoginModule", loginModuleReferencePatterns);
206                    uses.add(loginModuleUseGBeanData);
207                }
208                for (int i = uses.size() - 1; i >= 0; i--) {
209                    GBeanData data = uses.get(i);
210                    if (i > 0) {
211                        uses.get(i - 1).setReferencePattern("Next", data.getAbstractName());
212                    }
213                    context.addGBean(data);
214                }
215            } catch (GBeanAlreadyExistsException e) {
216                throw new DeploymentException(e);
217            } finally {
218                xmlCursor.dispose();
219            }
220            return uses.size() == 0 ? null : new ReferencePatterns(uses.get(0).getAbstractName());
221        }
222    
223        private LoginModuleControlFlag getControlFlag(String controlFlag) {
224            LoginModuleControlFlagEditor editor = new LoginModuleControlFlagEditor();
225            editor.setAsText(controlFlag);
226            return (LoginModuleControlFlag) editor.getValue();
227        }
228    
229        private String trim(String string) {
230            return string == null ? null : string.trim();
231        }
232    
233        public static final GBeanInfo GBEAN_INFO;
234    
235        private static final GReferenceInfo USE_REFERENCE_INFO;
236    
237        static {
238            GBeanInfoBuilder infoBuilder = GBeanInfoBuilder.createStatic(LoginConfigBuilder.class, "XmlReferenceBuilder");
239            infoBuilder.addAttribute("kernel", Kernel.class, false, false);
240            infoBuilder.addReference("xmlAttributeBuilders", XmlAttributeBuilder.class, "XmlAttributeBuilder");
241            infoBuilder.setConstructor(new String[]{"kernel", "xmlAttributeBuilders"});
242            infoBuilder.addInterface(XmlReferenceBuilder.class);
243            GBEAN_INFO = infoBuilder.getBeanInfo();
244    
245            Set<GReferenceInfo> referenceInfos = JaasLoginModuleUse.GBEAN_INFO.getReferences();
246            GReferenceInfo found = null;
247            for (GReferenceInfo testReferenceInfo : referenceInfos) {
248                String testRefName = testReferenceInfo.getName();
249                if (testRefName.equals("LoginModule")) {
250                    found = testReferenceInfo;
251                    break;
252                }
253            }
254            if (found == null) {
255                throw new RuntimeException("Someone changed the gbeaninfo on JaasLoginModuleUse");
256            }
257            USE_REFERENCE_INFO = found;
258    
259        }
260    
261        public static GBeanInfo getGBeanInfo() {
262            return GBEAN_INFO;
263        }
264    }