diff --git a/dashboard/public/css/login.css b/dashboard/public/css/login.css new file mode 100644 index 0000000..89ebe41 --- /dev/null +++ b/dashboard/public/css/login.css @@ -0,0 +1,293 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You 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. +*/ + +/* Generic */ +body { + font-family: 'Raleway', sans-serif; + background-color: #f6f7fb; + color: #686868; +} + +a { + color: #359f89; +} + +header { + background-color: #fff; + border-bottom: 1px #e7e8ed solid; +} + +textarea:focus, button:focus { + outline: none !important; +} + +textarea { + resize: none; +} + +/* Bootstrap Extended */ +.form-control:focus { + box-shadow: none; +} + +.breadcrumb { + padding: 8px 25px; + background-color: transparent; + margin-bottom: 0px; + font-weight: 600; + line-height: 44px; +} + +.breadcrumb>li+li:before { + padding: 0 10px; + font-family: FontAwesome; + color: #686868; + content: "\f105"; +} + +.well { + background-color: #f6f7fa; + border: 1px solid #e8e9ef; +} + +/* Header */ +.main-search .form-control { + border: none; + border-left: 1px #e7e8ed solid; + border-radius: 0px; + box-shadow: none; + height: 60px; + font-size: 18px; + font-style: italic; + -webkit-font-smoothing: antialiased; +} + +.main-search .input-group-addon { + font-size: 20px; + color: #b5b9bc; + background: none; + border: none; +} + +.page-title { + background-color: #fff; + padding: 50px; +} + +.page-title h1 { + margin-top: 0px; + margin-bottom: 20px; + font-weight: 600; + -webkit-font-smoothing: antialiased; +} + +.page-title h1 small { + position: relative; + bottom: 5px; + margin-left: 15px; + padding-left: 15px; + border-left: 1px #e8e9ef solid; + color: #b4b7bc; + font-size: 40%; + font-weight: 600; + text-transform: uppercase; +} + +.page-title .sub-title { + margin-bottom: 40px; +} + +/* Atlas Specific */ +.atlast-tabbable { + margin: 50px 0px; +} + +.atlast-tabbable .nav-tabs { + border-bottom: 1px solid #e8e9ee; +} + +.atlast-tabbable .nav-tabs>li>a { + padding: 15px 30px; + text-transform: uppercase; + letter-spacing: 1px; + border-radius: 2px 2px 0 0; +} + +.atlast-tabbable .nav-tabs>li.active>a, .atlast-tabbable .nav-tabs>li.active>a:focus, + .atlast-tabbable .nav-tabs>li.active>a:hover { + border: 1px solid #e8e9ee; + border-bottom-color: transparent; +} + +.atlast-tabbable .tab-content { + background-color: #fff; + padding: 35px; + border: 1px solid #e8e9ee; + border-top: none; +} + +.btn-atlas { + padding: 10px 20px; + background-color: #fff; + color: #37bb9b; + border: 1px #37bb9b solid; + border-radius: 4px; + -webkit-transition: all .3s ease; + -moz-transition: all .3s ease; + transition: all .3s ease; +} + +.btn-atlas:hover { + background-color: #37bb9b; + color: #fff; +} + +.atlas-tag { + padding: 6px 12px; + background-color: #4a90e2; + color: #fff; + font-size: 12px; + text-transform: uppercase; + border-radius: 4px; +} + +.atlas-tag i.fa { + position: relative; + right: -5px; + cursor: pointer; +} + +.btn-tag { + color: #4a90e2; + border: 1px #4a90e2 solid; + background-color: #fff; + border-radius: 4px; + -webkit-transition: all .3s ease; + -moz-transition: all .3s ease; + transition: all .3s ease; +} +/* Login Page */ +.login-pane { + margin-top: 50px; + background: #323544; + color: #eee; + padding: 15px; + border-radius: 5px; +} + +.login-pane h2 { + margin-bottom: 40px; +} + +.login-pane .input-group { + margin: 20px 0px; +} + +.login-pane .form-control, .login-pane .input-group-addon { + background-color: transparent; + border-radius: 0px; +} + +.login-pane .form-control { + border-left: none; + color: #eee; +} + +.login-pane .form-control:focus { + border-color: #CCC; +} + +.login-pane .input-group-addon { + border-right: none; + color: #ccc; +} + +.login-pane .form-control:focus+.input-group-addon { + border-color: #66afe9; +} + +.login-pane .btn-atlas { + background-color: #37bb9b; + color: #fff; +} + +.btn-tag:hover { + color: #fff; + background-color: #4a90e2; +} + +/* Login Page */ +.login-pane { + margin-top: 35%; + background: #323544; + color: #eee; + padding: 15px; + border-radius: 5px; +} + +.login-pane h2 { + margin-bottom: 40px; +} + +.login-pane .input-group { + margin: 20px 0px; +} + +.login-pane .form-control, .login-pane .input-group-addon { + background-color: transparent; + border-radius: 0px; +} + +.login-pane .form-control { + border-left: none; + color: #eee; +} + +.login-pane .form-control:focus { + border-color: #CCC; +} + +.login-pane .input-group-addon { + border-right: none; + color: #ccc; +} + +.login-pane .form-control:focus+.input-group-addon { + border-color: #66afe9; +} + +.login-pane .btn-atlas { + background-color: #37bb9b; + color: #fff; +} + +.errorBox { + position: absolute; + right: 36px; + display: none; + top: 26px; + width: 251px; +} + +.errorBox .alert { + box-shadow: 4px 3px 8px -2px gray; +} + +.close { + padding: 6px; + font-size: 15px; +} \ No newline at end of file diff --git a/dashboard/public/modules/home/views/header.html b/dashboard/public/modules/home/views/header.html index 896cfb4..c5bac05 100644 --- a/dashboard/public/modules/home/views/header.html +++ b/dashboard/public/modules/home/views/header.html @@ -42,6 +42,9 @@ <li data-ui-sref-active="active"> <a ng-if="!username" class="menu-link" href="javascript:void(0)" ng-click="showAbout()">About</a> </li> + <li data-ui-sref-active="active"> + <a ng-if="!username" class="menu-link" href="/logout.html">Logout</a> + </li> </ul> </nav> -</div> \ No newline at end of file +</div> diff --git a/distro/src/conf/atlas-application.properties b/distro/src/conf/atlas-application.properties index 7d32717..4131240 100755 --- a/distro/src/conf/atlas-application.properties +++ b/distro/src/conf/atlas-application.properties @@ -114,3 +114,11 @@ atlas.server.ha.enabled=false ## if ACLs need to be set on the created nodes, uncomment these lines and set the values ## #atlas.server.ha.zookeeper.acl=<scheme>:<id> #atlas.server.ha.zookeeper.auth=<scheme>:<authinfo> + + +#### atlas.login.method {FILE,LDAP,AD} #### +atlas.login.method=FILE + +### File path of users-credentials +atlas.login.credentials.file=${sys:atlas.home}/conf/users-credentials.properties + diff --git a/distro/src/conf/users-credentials.properties b/distro/src/conf/users-credentials.properties new file mode 100644 index 0000000..94e7cf4 --- /dev/null +++ b/distro/src/conf/users-credentials.properties @@ -0,0 +1,3 @@ +#username=password +admin=admin +user=user123 diff --git a/pom.xml b/pom.xml index 35ec380..2ccabc3 100755 --- a/pom.xml +++ b/pom.xml @@ -356,7 +356,10 @@ <guava.version>14.0</guava.version> <fastutil.version>6.5.16</fastutil.version> <guice.version>4.0</guice.version> - + <spring.version>3.1.3.RELEASE</spring.version> + <spring.security.version>3.1.3.RELEASE</spring.security.version> + <spring-ldap-core.version>1.3.1.RELEASE</spring-ldap-core.version> + <javax.servlet.version>3.1.0</javax.servlet.version> <!-- Needed for hooks --> <aopalliance.version>1.0</aopalliance.version> <commons-conf.version>1.10</commons-conf.version> @@ -1626,6 +1629,7 @@ <exclude>*.json</exclude> <exclude>**/overlays/**</exclude> <exclude>dev-support/**</exclude> + <exclude>**/users-credentials.properties</exclude> <exclude>**/public/css/animate.min.css</exclude> <exclude>**/public/css/fonts/**</exclude> <exclude>**/public/css/font-awesome.min.css</exclude> diff --git a/release-log.txt b/release-log.txt index d687990..b5476f8 100644 --- a/release-log.txt +++ b/release-log.txt @@ -3,6 +3,7 @@ Apache Atlas Release Notes --trunk - unreleased INCOMPATIBLE CHANGES: +ATLAS-494 UI Authentication (nixonrodrigues via shwethags) ATLAS-621 Introduce entity state in Id object (shwethags) ATLAS-474 Server does not start if the type is updated with same super type class information (dkantor via shwethags) ATLAS-479 Add description for different types during create time (guptaneeru via shwethags) diff --git a/webapp/pom.xml b/webapp/pom.xml index 45953e3..1df4112 100755 --- a/webapp/pom.xml +++ b/webapp/pom.xml @@ -231,6 +231,58 @@ </dependency> <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-core</artifactId> + <version>${spring.version}</version> + </dependency> + + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-web</artifactId> + <version>${spring.version}</version> + </dependency> + + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-webmvc</artifactId> + <version>${spring.version}</version> + </dependency> + + <dependency> + <groupId>org.springframework.security</groupId> + <artifactId>spring-security-core</artifactId> + <version>${spring.security.version}</version> + </dependency> + + <dependency> + <groupId>org.springframework.security</groupId> + <artifactId>spring-security-web</artifactId> + <version>${spring.security.version}</version> + </dependency> + + <dependency> + <groupId>org.springframework.security</groupId> + <artifactId>spring-security-config</artifactId> + <version>${spring.security.version}</version> + </dependency> + + <dependency> + <groupId>org.springframework.security</groupId> + <artifactId>spring-security-ldap</artifactId> + <version>${spring.security.version}</version> + </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + <version>${javax.servlet.version}</version> + </dependency> + <dependency> + <groupId>org.springframework.ldap</groupId> + <artifactId>spring-ldap-core</artifactId> + <version>${spring-ldap-core.version}</version> + </dependency> + + <dependency> <groupId>org.apache.atlas</groupId> <artifactId>atlas-dashboard</artifactId> <type>war</type> diff --git a/webapp/src/main/java/org/apache/atlas/util/PropertiesUtil.java b/webapp/src/main/java/org/apache/atlas/util/PropertiesUtil.java new file mode 100644 index 0000000..fef8efb --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/util/PropertiesUtil.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.atlas.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.apache.log4j.Logger; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; + +public class PropertiesUtil extends PropertyPlaceholderConfigurer { + private static Map<String, String> propertiesMap = new HashMap<String, String>(); + private static Logger logger = Logger.getLogger(PropertiesUtil.class); + protected List<String> xmlPropertyConfigurer = new ArrayList<String>(); + + private PropertiesUtil() { + + } + + @Override + protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props) + throws BeansException { + + Properties sysProps = System.getProperties(); + if (sysProps != null) { + for (String key : sysProps.stringPropertyNames()) { + String value = sysProps.getProperty(key); + if (value != null) { + value = value.trim(); + } + propertiesMap.put(key, value); + } + } + + Set<Object> keySet = props.keySet(); + for (Object key : keySet) { + String keyStr = key.toString(); + propertiesMap.put(keyStr, props.getProperty(keyStr).trim()); + } + + super.processProperties(beanFactory, props); + } + + public static String getProperty(String key, String defaultValue) { + if (key == null) { + return null; + } + String rtrnVal = propertiesMap.get(key); + if (rtrnVal == null) { + rtrnVal = defaultValue; + } + return rtrnVal; + } + + public static String getProperty(String key) { + if (key == null) { + return null; + } + return propertiesMap.get(key); + } + + public static String[] getPropertyStringList(String key) { + if (key == null) { + return null; + } + String value = propertiesMap.get(key); + if (value != null) { + String[] splitValues = value.split(","); + String[] returnValues = new String[splitValues.length]; + for (int i = 0; i < splitValues.length; i++) { + returnValues[i] = splitValues[i].trim(); + } + return returnValues; + } else { + return new String[0]; + } + } + + public static Integer getIntProperty(String key, int defaultValue) { + if (key == null) { + return null; + } + String rtrnVal = propertiesMap.get(key); + if (rtrnVal == null) { + return defaultValue; + } + return Integer.valueOf(rtrnVal); + } + + public static Integer getIntProperty(String key) { + if (key == null) { + return null; + } + String rtrnVal = propertiesMap.get(key); + if (rtrnVal == null) { + return null; + } + return Integer.valueOf(rtrnVal); + } + + public static boolean getBooleanProperty(String key, boolean defaultValue) { + if (key == null) { + return defaultValue; + } + String value = getProperty(key); + if (value == null) { + return defaultValue; + } + return Boolean.parseBoolean(value); + } +} \ No newline at end of file diff --git a/webapp/src/main/java/org/apache/atlas/util/XMLPropertiesUtil.java b/webapp/src/main/java/org/apache/atlas/util/XMLPropertiesUtil.java new file mode 100644 index 0000000..9c4f1c7 --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/util/XMLPropertiesUtil.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.atlas.util; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.apache.log4j.Logger; +import org.springframework.util.DefaultPropertiesPersister; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +public class XMLPropertiesUtil extends DefaultPropertiesPersister { + private static Logger logger = Logger.getLogger(XMLPropertiesUtil.class); + + public XMLPropertiesUtil() { + } + + @Override + public void loadFromXml(Properties properties, InputStream inputStream) + throws IOException { + try { + DocumentBuilderFactory xmlDocumentBuilderFactory = DocumentBuilderFactory + .newInstance(); + xmlDocumentBuilderFactory.setIgnoringComments(true); + xmlDocumentBuilderFactory.setNamespaceAware(true); + DocumentBuilder xmlDocumentBuilder = xmlDocumentBuilderFactory + .newDocumentBuilder(); + Document xmlDocument = xmlDocumentBuilder.parse(inputStream); + xmlDocument.getDocumentElement().normalize(); + + NodeList nList = xmlDocument.getElementsByTagName("property"); + + for (int temp = 0; temp < nList.getLength(); temp++) { + + Node nNode = nList.item(temp); + + if (nNode.getNodeType() == Node.ELEMENT_NODE) { + + Element eElement = (Element) nNode; + + String propertyName = ""; + String propertyValue = ""; + if (eElement.getElementsByTagName("name").item(0) != null) { + propertyName = eElement.getElementsByTagName("name") + .item(0).getTextContent().trim(); + } + if (eElement.getElementsByTagName("value").item(0) != null) { + propertyValue = eElement.getElementsByTagName("value") + .item(0).getTextContent().trim(); + } + + properties.put(propertyName, propertyValue); + + } + } + } catch (Exception e) { + logger.error("Error loading : ", e); + } + } + +} \ No newline at end of file diff --git a/webapp/src/main/java/org/apache/atlas/web/dao/UserDao.java b/webapp/src/main/java/org/apache/atlas/web/dao/UserDao.java new file mode 100644 index 0000000..76784c4 --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/dao/UserDao.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.atlas.web.dao; + +import com.google.common.annotations.VisibleForTesting; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; +import javax.annotation.PostConstruct; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Repository; +import org.apache.atlas.ApplicationProperties; +import org.apache.atlas.AtlasException; +import org.apache.atlas.web.model.User; +import org.apache.commons.configuration.Configuration; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +@Repository +public class UserDao { + + private static final Logger LOG = LoggerFactory.getLogger(UserDao.class); + + private Properties userLogins; + + @PostConstruct + public void init() { + loadFileLoginsDetails(); + } + + void loadFileLoginsDetails() { + String PROPERTY_FILE_PATH = null; + try { + + Configuration configuration = ApplicationProperties.get(); + PROPERTY_FILE_PATH = configuration + .getString("atlas.login.credentials.file"); + if (PROPERTY_FILE_PATH != null && !"".equals(PROPERTY_FILE_PATH)) { + userLogins = new Properties(); + userLogins.load(new FileInputStream(PROPERTY_FILE_PATH)); + }else { + LOG.error("Error while reading user.properties file, filepath=" + + PROPERTY_FILE_PATH); + } + + } catch (IOException | AtlasException e) { + LOG.error("Error while reading user.properties file, filepath=" + + PROPERTY_FILE_PATH, e); + } + } + + public User loadUserByUsername(final String username) + throws UsernameNotFoundException { + String password = userLogins.getProperty(username); + if (password == null || password.isEmpty()) { + throw new UsernameNotFoundException("Username not found." + + username); + } + User user = new User(); + user.setUsername(username); + user.setPassword(password); + return user; + } + + @VisibleForTesting + public void setUserLogins(Properties userLogins) { + this.userLogins = userLogins; + } + +} diff --git a/webapp/src/main/java/org/apache/atlas/web/filters/AtlasAuthenticationEntryPoint.java b/webapp/src/main/java/org/apache/atlas/web/filters/AtlasAuthenticationEntryPoint.java new file mode 100644 index 0000000..b77bd28 --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/filters/AtlasAuthenticationEntryPoint.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.atlas.web.filters; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.atlas.Atlas; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; + +@SuppressWarnings("deprecation") +class AtlasAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint { + + private static final Logger LOG = LoggerFactory.getLogger(Atlas.class); + + private String loginPath = "/login.jsp"; + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) + throws IOException, ServletException { + LOG.debug("redirecting to login page loginPath" + loginPath); + + response.sendRedirect(loginPath); + } +} diff --git a/webapp/src/main/java/org/apache/atlas/web/model/User.java b/webapp/src/main/java/org/apache/atlas/web/model/User.java new file mode 100644 index 0000000..54a19c5 --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/model/User.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliRance 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.apache.atlas.web.model; + +import java.util.Collection; +import java.util.List; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +public class User implements UserDetails { + private static final long serialVersionUID = 1L; + + private String username; + private String password; + private List<GrantedAuthority> authorities; + private boolean accountNonExpired = true; + private boolean accountNonLocked = true; + private boolean credentialsNonExpired = true; + private boolean enabled = true; + + public User(String userName2, String userPassword, + List<GrantedAuthority> grantedAuths) { + this.username = userName2; + this.password = userPassword; + this.authorities = grantedAuths; + + } + + public User() { + } + + @Override + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + @Override + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @Override + public Collection<? extends GrantedAuthority> getAuthorities() { + return this.authorities; + } + + public void setAuthorities(List<GrantedAuthority> authorities) { + this.authorities = authorities; + } + + @Override + public boolean isAccountNonExpired() { + return this.accountNonExpired; + } + + public void setAccountNonExpired(boolean accountNonExpired) { + this.accountNonExpired = accountNonExpired; + } + + @Override + public boolean isAccountNonLocked() { + return this.accountNonLocked; + } + + public void setAccountNonLocked(boolean accountNonLocked) { + this.accountNonLocked = accountNonLocked; + } + + @Override + public boolean isCredentialsNonExpired() { + return this.credentialsNonExpired; + } + + public void setCredentialsNonExpired(boolean credentialsNonExpired) { + this.credentialsNonExpired = credentialsNonExpired; + } + + @Override + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("User [username="); + builder.append(username); + builder.append(", authorities="); + builder.append(authorities); + builder.append(", accountNonExpired="); + builder.append(accountNonExpired); + builder.append(", accountNonLocked="); + builder.append(accountNonLocked); + builder.append(", credentialsNonExpired="); + builder.append(credentialsNonExpired); + builder.append(", enabled="); + builder.append(enabled); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/webapp/src/main/java/org/apache/atlas/web/security/AtlasADAuthenticationProvider.java b/webapp/src/main/java/org/apache/atlas/web/security/AtlasADAuthenticationProvider.java new file mode 100644 index 0000000..96dca45 --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/security/AtlasADAuthenticationProvider.java @@ -0,0 +1,153 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.atlas.web.security; + +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.apache.atlas.util.PropertiesUtil; +import org.apache.atlas.web.model.User; +import org.apache.log4j.Logger; +import org.springframework.ldap.core.support.LdapContextSource; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.ldap.DefaultSpringSecurityContextSource; +import org.springframework.security.ldap.authentication.BindAuthenticator; +import org.springframework.security.ldap.authentication.LdapAuthenticationProvider; +import org.springframework.security.ldap.search.FilterBasedLdapUserSearch; +import org.springframework.stereotype.Component; + +@Component +public class AtlasADAuthenticationProvider extends + AtlasAbstractAuthenticationProvider { + private static Logger LOG = Logger + .getLogger(AtlasADAuthenticationProvider.class); + + private String adURL; + private String adBindDN; + private String adBindPassword; + private String adUserSearchFilter; + private String adBase; + private String adReferral; + private String adDefaultRole; + + @PostConstruct + public void setup() { + setADProperties(); + } + + @Override + public Authentication authenticate(Authentication authentication) + throws AuthenticationException { + try { + return getADBindAuthentication(authentication); + } catch (Exception e) { + throw new AtlasAuthenticationException(e.getMessage(), e.getCause()); + } + } + + private Authentication getADBindAuthentication(Authentication authentication) + throws Exception { + try { + String userName = authentication.getName(); + String userPassword = ""; + if (authentication.getCredentials() != null) { + userPassword = authentication.getCredentials().toString(); + } + LdapContextSource ldapContextSource = getLdapContextSource(); + + if (adUserSearchFilter == null + || adUserSearchFilter.trim().isEmpty()) { + adUserSearchFilter = "(sAMAccountName={0})"; + } + + BindAuthenticator bindAuthenticator = getBindAuthenticator(ldapContextSource); + + LdapAuthenticationProvider ldapAuthenticationProvider = new LdapAuthenticationProvider( + bindAuthenticator); + if (userName != null && userPassword != null + && !userName.trim().isEmpty() + && !userPassword.trim().isEmpty()) { + final List<GrantedAuthority> grantedAuths = getAuthorities(userName); + final UserDetails principal = new User(userName, userPassword, + grantedAuths); + final Authentication finalAuthentication = new UsernamePasswordAuthenticationToken( + principal, userPassword, grantedAuths); + authentication = ldapAuthenticationProvider + .authenticate(finalAuthentication); + authentication = getAuthenticationWithGrantedAuthority(authentication); + return authentication; + } else { + throw new AtlasAuthenticationException( + "AD Authentication Failed userName or userPassword is null or empty"); + } + } catch (Exception e) { + LOG.error("AD Authentication Failed:", e); + throw new AtlasAuthenticationException("AD Authentication Failed ", + e); + } + } + + private void setADProperties() { + adURL = PropertiesUtil.getProperty("atlas.ad.url", adURL); + adBindDN = PropertiesUtil.getProperty("atlas.ad.bind.dn", adBindDN); + adBindPassword = PropertiesUtil.getProperty("atlas.ad.bind.password", + adBindPassword); + adUserSearchFilter = PropertiesUtil.getProperty( + "atlas.ad.user.searchfilter", adUserSearchFilter); + adBase = PropertiesUtil.getProperty("atlas.ad.base.dn", adBase); + adReferral = PropertiesUtil + .getProperty("atlas.ad.referral", adReferral); + adDefaultRole = PropertiesUtil.getProperty("atlas.ad.default.role", + adDefaultRole); + } + + private LdapContextSource getLdapContextSource() throws Exception { + + LdapContextSource ldapContextSource = new DefaultSpringSecurityContextSource( + adURL); + ldapContextSource.setUserDn(adBindDN); + ldapContextSource.setPassword(adBindPassword); + ldapContextSource.setReferral(adReferral); + ldapContextSource.setCacheEnvironmentProperties(true); + ldapContextSource.setAnonymousReadOnly(false); + ldapContextSource.setPooled(true); + ldapContextSource.afterPropertiesSet(); + + return ldapContextSource; + + } + + private BindAuthenticator getBindAuthenticator( + LdapContextSource ldapContextSource) throws Exception { + FilterBasedLdapUserSearch userSearch = new FilterBasedLdapUserSearch( + adBase, adUserSearchFilter, ldapContextSource); + userSearch.setSearchSubtree(true); + BindAuthenticator bindAuthenticator = new BindAuthenticator( + ldapContextSource); + bindAuthenticator.setUserSearch(userSearch); + bindAuthenticator.afterPropertiesSet(); + return bindAuthenticator; + } + +} diff --git a/webapp/src/main/java/org/apache/atlas/web/security/AtlasAbstractAuthenticationProvider.java b/webapp/src/main/java/org/apache/atlas/web/security/AtlasAbstractAuthenticationProvider.java new file mode 100644 index 0000000..b2b11da --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/security/AtlasAbstractAuthenticationProvider.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.atlas.web.security; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; + +public abstract class AtlasAbstractAuthenticationProvider implements + AuthenticationProvider { + + @Override + public boolean supports(Class<?> authentication) { + return UsernamePasswordAuthenticationToken.class + .isAssignableFrom(authentication); + } + + /** + * + * @param authentication + * @return + */ + public Authentication getAuthenticationWithGrantedAuthority( + Authentication authentication) { + UsernamePasswordAuthenticationToken result = null; + if (authentication != null && authentication.isAuthenticated()) { + final List<GrantedAuthority> grantedAuths = getAuthorities(authentication + .getName().toString()); + final UserDetails userDetails = new User(authentication.getName() + .toString(), authentication.getCredentials().toString(), + grantedAuths); + result = new UsernamePasswordAuthenticationToken(userDetails, + authentication.getCredentials(), grantedAuths); + result.setDetails(authentication.getDetails()); + return result; + } + return authentication; + } + + /** + * This method will be modified when actual roles are introduced. + * + */ + protected List<GrantedAuthority> getAuthorities(String username) { + final List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>(); + grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER")); + return grantedAuths; + } + +} diff --git a/webapp/src/main/java/org/apache/atlas/web/security/AtlasAuthenticationException.java b/webapp/src/main/java/org/apache/atlas/web/security/AtlasAuthenticationException.java new file mode 100644 index 0000000..c1b0f82 --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/security/AtlasAuthenticationException.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.atlas.web.security; + +import org.springframework.security.core.AuthenticationException; + +public class AtlasAuthenticationException extends AuthenticationException { + + public AtlasAuthenticationException(String message) { + super(message); + } + + public AtlasAuthenticationException(String message, Throwable cause) { + super(message, cause); + } + +} \ No newline at end of file diff --git a/webapp/src/main/java/org/apache/atlas/web/security/AtlasAuthenticationProvider.java b/webapp/src/main/java/org/apache/atlas/web/security/AtlasAuthenticationProvider.java new file mode 100644 index 0000000..7f87bd5 --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/security/AtlasAuthenticationProvider.java @@ -0,0 +1,93 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.atlas.web.security; + +import javax.annotation.PostConstruct; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.stereotype.Component; +import org.apache.atlas.ApplicationProperties; +import org.apache.commons.configuration.Configuration; + +@Component +public class AtlasAuthenticationProvider extends + AtlasAbstractAuthenticationProvider { + private static final Logger LOG = LoggerFactory + .getLogger(AtlasAuthenticationProvider.class); + + private String atlasAuthenticationMethod = "UNKNOWN"; + + enum AUTH_METHOD { + FILE, LDAP, AD + }; + + @Autowired + AtlasLdapAuthenticationProvider ldapAuthenticationProvider; + + @Autowired + AtlasFileAuthenticationProvider fileAuthenticationProvider; + + @Autowired + AtlasADAuthenticationProvider adAuthenticationProvider; + + @PostConstruct + void setAuthenticationMethod() { + try { + Configuration configuration = ApplicationProperties.get(); + this.atlasAuthenticationMethod = configuration.getString( + "atlas.login.method", "UNKNOWN"); + } catch (Exception e) { + LOG.error( + "Error while getting atlas.login.method application properties", + e); + } + } + + @Override + public Authentication authenticate(Authentication authentication) + throws AuthenticationException { + + if (atlasAuthenticationMethod.equalsIgnoreCase(AUTH_METHOD.FILE.name())) { + authentication = fileAuthenticationProvider + .authenticate(authentication); + } else if (atlasAuthenticationMethod.equalsIgnoreCase(AUTH_METHOD.LDAP + .name())) { + authentication = ldapAuthenticationProvider + .authenticate(authentication); + } else if (atlasAuthenticationMethod.equalsIgnoreCase(AUTH_METHOD.AD + .name())) { + authentication = adAuthenticationProvider + .authenticate(authentication); + } else { + LOG.error("Invalid authentication method :" + + atlasAuthenticationMethod); + } + + if (authentication != null && authentication.isAuthenticated()) { + return authentication; + } else { + LOG.error("Authentication failed."); + throw new AtlasAuthenticationException("Authentication failed."); + } + } + + +} diff --git a/webapp/src/main/java/org/apache/atlas/web/security/AtlasFileAuthenticationProvider.java b/webapp/src/main/java/org/apache/atlas/web/security/AtlasFileAuthenticationProvider.java new file mode 100644 index 0000000..b3e3354 --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/security/AtlasFileAuthenticationProvider.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliRance 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.apache.atlas.web.security; + +import java.util.Collection; + +import org.apache.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Component; + +@Component +public class AtlasFileAuthenticationProvider extends AtlasAbstractAuthenticationProvider { + + private static Logger logger = Logger.getLogger(AtlasFileAuthenticationProvider.class); + + @Autowired + private UserDetailsService userDetailsService; + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + String username = authentication.getName(); + String password = (String) authentication.getCredentials(); + if (username == null || username.isEmpty()) { + logger.error("Username can't be null or empty."); + throw new BadCredentialsException( + "Username can't be null or empty."); + } + if (password == null || password.isEmpty()) { + logger.error("Password can't be null or empty."); + throw new BadCredentialsException( + "Password can't be null or empty."); + } + + UserDetails user = userDetailsService.loadUserByUsername(username); + + if (!password.equals(user.getPassword())) { + logger.error("Wrong password " + username); + throw new BadCredentialsException("Wrong password"); + } + Collection<? extends GrantedAuthority> authorities = getAuthorities(username); + authentication = new UsernamePasswordAuthenticationToken(username, password, authorities); + + authentication = getAuthenticationWithGrantedAuthority(authentication); + + return authentication; + } + +} diff --git a/webapp/src/main/java/org/apache/atlas/web/security/AtlasLdapAuthenticationProvider.java b/webapp/src/main/java/org/apache/atlas/web/security/AtlasLdapAuthenticationProvider.java new file mode 100644 index 0000000..d911c1b --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/security/AtlasLdapAuthenticationProvider.java @@ -0,0 +1,186 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.atlas.web.security; + +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.apache.atlas.util.PropertiesUtil; +import org.apache.atlas.web.model.User; +import org.apache.log4j.Logger; +import org.springframework.ldap.core.support.LdapContextSource; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.ldap.DefaultSpringSecurityContextSource; +import org.springframework.security.ldap.authentication.BindAuthenticator; +import org.springframework.security.ldap.authentication.LdapAuthenticationProvider; +import org.springframework.security.ldap.search.FilterBasedLdapUserSearch; +import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator; +import org.springframework.stereotype.Component; + +@Component +public class AtlasLdapAuthenticationProvider extends + AtlasAbstractAuthenticationProvider { + private static Logger LOG = Logger + .getLogger(AtlasLdapAuthenticationProvider.class); + + private String ldapURL; + private String ldapUserDNPattern; + private String ldapGroupSearchBase; + private String ldapGroupSearchFilter; + private String ldapGroupRoleAttribute; + private String ldapBindDN; + private String ldapBindPassword; + private String ldapDefaultRole; + private String ldapUserSearchFilter; + private String ldapReferral; + private String ldapBase; + + @PostConstruct + public void setup() { + setLdapProperties(); + } + + @Override + public Authentication authenticate(Authentication authentication) + throws AuthenticationException { + try { + return getLdapBindAuthentication(authentication); + } catch (Exception e) { + throw new AtlasAuthenticationException(e.getMessage(), e.getCause()); + } + } + + private Authentication getLdapBindAuthentication( + Authentication authentication) throws Exception { + try { + String userName = authentication.getName(); + String userPassword = ""; + if (authentication.getCredentials() != null) { + userPassword = authentication.getCredentials().toString(); + } + + LdapContextSource ldapContextSource = getLdapContextSource(); + + DefaultLdapAuthoritiesPopulator defaultLdapAuthoritiesPopulator = getDefaultLdapAuthoritiesPopulator(ldapContextSource); + + if (ldapUserSearchFilter == null + || ldapUserSearchFilter.trim().isEmpty()) { + ldapUserSearchFilter = "(uid={0})"; + } + + FilterBasedLdapUserSearch userSearch = new FilterBasedLdapUserSearch( + ldapBase, ldapUserSearchFilter, ldapContextSource); + userSearch.setSearchSubtree(true); + + BindAuthenticator bindAuthenticator = getBindAuthenticator( + userSearch, ldapContextSource); + + LdapAuthenticationProvider ldapAuthenticationProvider = new LdapAuthenticationProvider( + bindAuthenticator, defaultLdapAuthoritiesPopulator); + + if (userName != null && userPassword != null + && !userName.trim().isEmpty() + && !userPassword.trim().isEmpty()) { + final List<GrantedAuthority> grantedAuths = getAuthorities(userName); + final UserDetails principal = new User(userName, userPassword, + grantedAuths); + final Authentication finalAuthentication = new UsernamePasswordAuthenticationToken( + principal, userPassword, grantedAuths); + authentication = ldapAuthenticationProvider + .authenticate(finalAuthentication); + authentication = getAuthenticationWithGrantedAuthority(authentication); + return authentication; + } else { + throw new AtlasAuthenticationException( + "LDAP Authentication::userName or userPassword is null or empty for userName " + + userName); + } + } catch (Exception e) { + LOG.error("LDAP Authentication Failed:", e); + throw new AtlasAuthenticationException( + "LDAP Authentication Failed", e); + } + } + + private void setLdapProperties() { + ldapURL = PropertiesUtil.getProperty("atlas.ldap.url", ldapURL); + ldapUserDNPattern = PropertiesUtil.getProperty( + "atlas.ldap.user.dnpattern", ldapUserDNPattern); + ldapGroupSearchBase = PropertiesUtil.getProperty( + "atlas.ldap.group.searchbase", ldapGroupSearchBase); + ldapGroupSearchFilter = PropertiesUtil.getProperty( + "atlas.ldap.group.searchfilter", ldapGroupSearchFilter); + ldapGroupRoleAttribute = PropertiesUtil.getProperty( + "atlas.ldap.group.roleattribute", ldapGroupRoleAttribute); + ldapBindDN = PropertiesUtil.getProperty("atlas.ldap.bind.dn", + ldapBindDN); + ldapBindPassword = PropertiesUtil.getProperty( + "atlas.ldap.bind.password", ldapBindDN); + ldapDefaultRole = PropertiesUtil.getProperty("atlas.ldap.default.role", + ldapDefaultRole); + ldapUserSearchFilter = PropertiesUtil.getProperty( + "atlas.ldap.user.searchfilter", ldapUserSearchFilter); + ldapReferral = PropertiesUtil.getProperty("atlas.ldap.referral", + ldapReferral); + ldapBase = PropertiesUtil.getProperty("atlas.ldap.base.dn", ldapBase); + } + + private LdapContextSource getLdapContextSource() throws Exception { + LdapContextSource ldapContextSource = new DefaultSpringSecurityContextSource( + ldapURL); + ldapContextSource.setUserDn(ldapBindDN); + ldapContextSource.setPassword(ldapBindPassword); + ldapContextSource.setReferral(ldapReferral); + ldapContextSource.setCacheEnvironmentProperties(false); + ldapContextSource.setAnonymousReadOnly(false); + ldapContextSource.setPooled(true); + ldapContextSource.afterPropertiesSet(); + return ldapContextSource; + } + + private DefaultLdapAuthoritiesPopulator getDefaultLdapAuthoritiesPopulator( + LdapContextSource ldapContextSource) { + DefaultLdapAuthoritiesPopulator defaultLdapAuthoritiesPopulator = new DefaultLdapAuthoritiesPopulator( + ldapContextSource, ldapGroupSearchBase); + defaultLdapAuthoritiesPopulator + .setGroupRoleAttribute(ldapGroupRoleAttribute); + defaultLdapAuthoritiesPopulator + .setGroupSearchFilter(ldapGroupSearchFilter); + defaultLdapAuthoritiesPopulator.setIgnorePartialResultException(true); + + return defaultLdapAuthoritiesPopulator; + } + + private BindAuthenticator getBindAuthenticator( + FilterBasedLdapUserSearch userSearch, + LdapContextSource ldapContextSource) throws Exception { + BindAuthenticator bindAuthenticator = new BindAuthenticator( + ldapContextSource); + bindAuthenticator.setUserSearch(userSearch); + String[] userDnPatterns = new String[] { ldapUserDNPattern }; + bindAuthenticator.setUserDnPatterns(userDnPatterns); + bindAuthenticator.afterPropertiesSet(); + return bindAuthenticator; + } +} diff --git a/webapp/src/main/java/org/apache/atlas/web/service/UserService.java b/webapp/src/main/java/org/apache/atlas/web/service/UserService.java new file mode 100644 index 0000000..33101e2 --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/service/UserService.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.atlas.web.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import org.apache.atlas.web.dao.UserDao; +import org.apache.atlas.web.service.UserService; +import org.apache.atlas.web.model.User; + +@Service +public class UserService implements UserDetailsService { + + @Autowired + private UserDao userDao; + + @Override + public User loadUserByUsername(final String username) + throws UsernameNotFoundException { + return userDao.loadUserByUsername(username); + } +} diff --git a/webapp/src/main/resources/atlas-admin-site.xml b/webapp/src/main/resources/atlas-admin-site.xml new file mode 100644 index 0000000..ab8ba3d --- /dev/null +++ b/webapp/src/main/resources/atlas-admin-site.xml @@ -0,0 +1,129 @@ +<!-- 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. + See accompanying LICENSE file. --> + + +<configuration> + <!-- # Ldap info start --> + + <property> + <name>atlas.ldap.url</name> + <display-name>LDAP URL</display-name> + <value>ldap://172.22.98.129:389</value> + <description>LDAP Server URL, only used if + Authentication method + is LDAP + </description> + </property> + <property> + <name>atlas.ldap.user.dnpattern</name> + <value>uid={0},ou=People,dc=example,dc=com</value> + <description></description> + </property> + <property> + <name>atlas.ldap.group.searchbase</name> + <display-name>Group Search Base</display-name> + <value>dc=example,dc=com</value> + <description></description> + </property> + <property> + <name>atlas.ldap.group.searchfilter</name> + <display-name>Group Search Filter</display-name> + <value>(member=uid={0},ou=People,dc=example,dc=com) + </value> + <description></description> + </property> + <property> + <name>atlas.ldap.group.roleattribute</name> + <value>cn</value> + <description></description> + </property> + + <property> + <name>atlas.ldap.base.dn</name> + <value>dc=example,dc=com</value> + <description>LDAP base dn or search base</description> + </property> + <property> + <name>atlas.ldap.bind.dn</name> + <display-name>Bind User</display-name> + <value>cn=Manager,dc=example,dc=com</value> + <description>LDAP bind dn or manager dn</description> + </property> + <property> + <name>atlas.ldap.bind.password</name> + <display-name>​Bind User Password</display-name> + <value>p@ssword</value> + <property-type>PASSWORD</property-type> + <description>Password for the account that can search + for users + </description> + <value-attributes> + <type>password</type> + <overridable>false</overridable> + </value-attributes> + </property> + <property> + <name>atlas.ldap.user.searchfilter</name> + <display-name>User Search Filter</display-name> + <value>(uid={0})</value> + <description></description> + </property> + + <property> + <name>atlas.ldap.default.role</name> + <value>ROLE_USER</value> + </property> + <property> + <name>atlas.ldap.referral</name> + <value>ignore</value> + <description>follow or ignore</description> + </property> + + + <!-- # Ldap Info end --> + + <!-- #AD info start --> + <property> + <name>atlas.ad.url</name> + <value>ldap://172.25.16.111:389</value> + <description></description> + </property> + + <property> + <name>atlas.ad.bind.dn</name> + <value>CN=team,CN=Users,DC=SME,DC=support,DC=com</value> + <description>AD bind dn or manager dn</description> + </property> + <property> + <name>atlas.ad.bind.password</name> + <value>Abcd1234!!</value> + <description>AD bind password</description> + </property> + <property> + <name>atlas.ad.user.searchfilter</name> + <display-name>User Search Filter</display-name> + <value>(sAMAccountName={0})</value> + <description></description> + </property> + <property> + <name>atlas.ad.base.dn</name> + <value>DC=SME,DC=support,DC=com</value> + <description>AD base dn or search base</description> + </property> + <property> + <name>atlas.ad.referral</name> + <value>ignore</value> + <description>follow or ignore</description> + </property> + <property> + <name>atlas.ad.default.role</name> + <value>ROLE_USER</value> + </property> + <!-- AD info end --> +</configuration> \ No newline at end of file diff --git a/webapp/src/main/resources/spring-security.xml b/webapp/src/main/resources/spring-security.xml new file mode 100644 index 0000000..8533473 --- /dev/null +++ b/webapp/src/main/resources/spring-security.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor + license agreements. See the NOTICE file distributed with this work for additional + information regarding copyright ownership. The ASF licenses this file to + You 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. --> + +<beans:beans xmlns="http://www.springframework.org/schema/security" + xmlns:beans="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:security="http://www.springframework.org/schema/security" + xmlns:util="http://www.springframework.org/schema/util" + xmlns:oauth="http://www.springframework.org/schema/security/oauth2" + xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation="http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans-3.1.xsd + http://www.springframework.org/schema/security + http://www.springframework.org/schema/security/spring-security-3.1.xsd + http://www.springframework.org/schema/util + http://www.springframework.org/schema/util/spring-util-3.1.xsd + http://www.springframework.org/schema/security/oauth2 + http://www.springframework.org/schema/security/spring-security-oauth2-1.0.xsd + http://www.springframework.org/schema/context + http://www.springframework.org/schema/context/spring-context-3.1.xsd"> + + <security:http pattern="/login.jsp" security="none" /> + <security:http pattern="/css/**" security="none" /> + <security:http pattern="/lib/**" security="none" /> + + <security:http disable-url-rewriting="true" + use-expressions="true" create-session="always" + entry-point-ref="authenticationProcessingFilterEntryPoint"> + <security:session-management + session-fixation-protection="newSession" /> + <intercept-url pattern="/**" access="isAuthenticated()" /> + <security:custom-filter position="FORM_LOGIN_FILTER" + ref="atlasUsernamePasswordAuthenticationFilter" /> + <security:logout delete-cookies="JSESSIONID" + logout-url="/logout.html" /> + <http-basic entry-point-ref="authenticationProcessingFilterEntryPoint" /> + </security:http> + + <beans:bean id="atlasUsernamePasswordAuthenticationFilter" + class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"> + <beans:property name="authenticationManager" + ref="authenticationManager" /> + <beans:property name="authenticationSuccessHandler" + ref="ajaxAuthSuccessHandler" /> + <beans:property name="authenticationFailureHandler" + ref="ajaxAuthFailureHandler" /> + </beans:bean> + + <beans:bean id="authenticationProcessingFilterEntryPoint" + class="org.apache.atlas.web.filters.AtlasAuthenticationEntryPoint"> + <beans:property name="loginFormUrl" + value="/login.jsp" /> + <beans:property name="forceHttps" value="false" /> + </beans:bean> + + <beans:bean id="ajaxAuthSuccessHandler" + class="org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler"> + <beans:property name="defaultTargetUrl" + value="/index.html" /> + </beans:bean> + + <beans:bean id="ajaxAuthFailureHandler" + class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"> + <beans:property name="defaultFailureUrl" + value="/login.jsp?login_error=true " /> + </beans:bean> + + <beans:bean id="atlasAuthenticationProvider" + class="org.apache.atlas.web.security.AtlasAuthenticationProvider"> + </beans:bean> + + <security:authentication-manager + alias="authenticationManager"> + <security:authentication-provider + ref="atlasAuthenticationProvider" /> + </security:authentication-manager> + + <security:global-method-security + pre-post-annotations="enabled" /> + + <context:component-scan base-package="org.apache.atlas.web" /> + +</beans:beans> diff --git a/webapp/src/main/webapp/WEB-INF/applicationContext.xml b/webapp/src/main/webapp/WEB-INF/applicationContext.xml new file mode 100644 index 0000000..b58952c --- /dev/null +++ b/webapp/src/main/webapp/WEB-INF/applicationContext.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor + license agreements. See the NOTICE file distributed with this work for additional + information regarding copyright ownership. The ASF licenses this file to + You 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. --> + +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:mvc="http://www.springframework.org/schema/mvc" + xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans-3.1.xsd + http://www.springframework.org/schema/mvc + http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd + http://www.springframework.org/schema/context + http://www.springframework.org/schema/context/spring-context-3.1.xsd"> + + + <import resource="classpath:/spring-security.xml" /> + <bean id="xmlPropertyConfigurer" class="org.apache.atlas.util.XMLPropertiesUtil" /> + + <bean id="propertyConfigurer" class="org.apache.atlas.util.PropertiesUtil"> + <property name="locations"> + <list> + <value>classpath:atlas-admin-site.xml + </value> + </list> + </property> + <property name="propertiesPersister" ref="xmlPropertyConfigurer" /> + </bean> + +</beans> \ No newline at end of file diff --git a/webapp/src/main/webapp/WEB-INF/web.xml b/webapp/src/main/webapp/WEB-INF/web.xml index 855a068..deb97d9 100755 --- a/webapp/src/main/webapp/WEB-INF/web.xml +++ b/webapp/src/main/webapp/WEB-INF/web.xml @@ -50,4 +50,27 @@ <listener> <listener-class>org.apache.atlas.web.listeners.GuiceServletConfig</listener-class> </listener> -</web-app> + + <listener> + <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> + </listener> + + <listener> + <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> + </listener> + + <listener> + <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> + </listener> + + <filter> + <filter-name>springSecurityFilterChain</filter-name> + <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> + </filter> + + <filter-mapping> + <filter-name>springSecurityFilterChain</filter-name> + <url-pattern>/*</url-pattern> + </filter-mapping> + +</web-app> \ No newline at end of file diff --git a/webapp/src/main/webapp/login.jsp b/webapp/src/main/webapp/login.jsp new file mode 100644 index 0000000..2990477 --- /dev/null +++ b/webapp/src/main/webapp/login.jsp @@ -0,0 +1,90 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You 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. +--> +<%@ page import="org.apache.atlas.ApplicationProperties,org.apache.commons.configuration.Configuration" %> +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>Atlas Login</title> + <meta name="description" content="description"> + <meta name="keyword" content="keywords"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"> + <link href="http://netdna.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.css" rel="stylesheet"> + <link href='http://fonts.googleapis.com/css?family=Righteous' rel='stylesheet' type='text/css'> + <link href="/css/login.css" rel="stylesheet"> + <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> + <!--[if lt IE 9]> + <script src="http://getbootstrap.com/docs-assets/js/html5shiv.js"></script> + <script src="http://getbootstrap.com/docs-assets/js/respond.min.js"></script> + <![endif]--> + + <script src="https://code.jquery.com/jquery-2.2.1.min.js" integrity="sha256-gvQgAFzTH6trSrAWoH1iPo9Xc96QxSZ3feW6kem+O00=" crossorigin="anonymous"></script> + + </head> +<body> +<div class="errorBox"> + <a href="javascript:void(0)" class="close" title="close"><i class="fa fa-times"></i></a> + <div class="alert alert-danger"> + <strong>Error!</strong> Invalid User credentials.<br> Please try again. + </div> +</div> + +<div id="wrapper"> + <div class="container-fluid"> + <div class="row"> + <div class="col-sm-4 col-sm-offset-4"> + <form name='f' action='/j_spring_security_check' method='POST'> + <div class="login-pane"> + <h2 align="center">Apache <strong>Atlas</strong></h2> + <div class="input-group"> + <span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span> + <input type='text' class="form-control" name='j_username' placeholder="Username" required > + </div> + <div class="input-group"> + <span class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></span> + <input type='password' name='j_password' class="form-control" placeholder="Password" required > + + </div> + <input class="btn-atlas btn-block" name="submit" type="submit" value="Login"/> + + </div> + </form> + </div> + </div> + </div> +</div> + +<script type="text/javascript"> +$('body').ready(function(){ + var query = window.location.search.substring(1); + var statusArr = query.split('='); + var status = -1; + if(statusArr.length > 0){ + status = statusArr[1]; + } + if(status=="true"){ + $('.errorBox').show(); + } +}); +$('.close').click(function(){ + $('.errorBox').hide(); +}) +</script> + +</body> +</html> diff --git a/webapp/src/test/java/org/apache/atlas/web/security/FileAuthenticationTest.java b/webapp/src/test/java/org/apache/atlas/web/security/FileAuthenticationTest.java new file mode 100644 index 0000000..7e4c2c8 --- /dev/null +++ b/webapp/src/test/java/org/apache/atlas/web/security/FileAuthenticationTest.java @@ -0,0 +1,145 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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.apache.atlas.web.security; + +import java.io.File; +import org.apache.atlas.ApplicationProperties; +import org.apache.atlas.web.TestUtils; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.commons.io.FileUtils; +import org.junit.Assert; +import org.mockito.Mock; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import static org.mockito.Mockito.when; + +public class FileAuthenticationTest { + + private static ApplicationContext applicationContext = null; + private static AtlasAuthenticationProvider authProvider = null; + private String originalConf; + private static final Logger LOG = LoggerFactory + .getLogger(FileAuthenticationTest.class); + @Mock + Authentication authentication; + + @BeforeMethod + public void setup1() { + MockitoAnnotations.initMocks(this); + } + + @BeforeClass + public void setup() throws Exception { + + String persistDir = TestUtils.getTempDirectory(); + + setupUserCredential(persistDir); + + setUpAltasApplicationProperties(persistDir); + + originalConf = System.getProperty("atlas.conf"); + System.setProperty("atlas.conf", persistDir); + + applicationContext = new ClassPathXmlApplicationContext( + "spring-security.xml"); + authProvider = applicationContext + .getBean(org.apache.atlas.web.security.AtlasAuthenticationProvider.class); + + } + + private void setUpAltasApplicationProperties(String persistDir) throws Exception{ + final PropertiesConfiguration configuration = new PropertiesConfiguration(); + configuration.setProperty("atlas.login.method", "FILE"); + configuration.setProperty("atlas.login.credentials.file", persistDir + + "/users-credentials"); + + TestUtils.writeConfiguration(configuration, persistDir + File.separator + + ApplicationProperties.APPLICATION_PROPERTIES); + + } + + private void setupUserCredential(String tmpDir) throws Exception { + + StringBuilder credentialFileStr = new StringBuilder(1024); + credentialFileStr.append("admin=admin123\n"); + credentialFileStr.append("user=user123\n"); + credentialFileStr.append("test=test123\n"); + File credentialFile = new File(tmpDir, "users-credentials"); + FileUtils.write(credentialFile, credentialFileStr.toString()); + } + + @Test + public void testValidUserLogin() { + + when(authentication.getName()).thenReturn("admin"); + when(authentication.getCredentials()).thenReturn("admin123"); + + Authentication auth = authProvider.authenticate(authentication); + LOG.debug(" " + auth); + + Assert.assertTrue(auth.isAuthenticated()); + } + + @Test + public void testInValidPasswordLogin() { + + when(authentication.getName()).thenReturn("admin"); + when(authentication.getCredentials()).thenReturn("wrongpassword"); + + try { + Authentication auth = authProvider.authenticate(authentication); + LOG.debug(" " + auth); + } catch (BadCredentialsException bcExp) { + Assert.assertEquals("Wrong password", bcExp.getMessage()); + } + } + + @Test + public void testInValidUsernameLogin() { + + when(authentication.getName()).thenReturn("wrongUserName"); + when(authentication.getCredentials()).thenReturn("wrongpassword"); + try { + Authentication auth = authProvider.authenticate(authentication); + LOG.debug(" " + auth); + } catch (UsernameNotFoundException uExp) { + Assert.assertTrue(uExp.getMessage().contains("Username not found.")); + } + } + + @AfterClass + public void tearDown() throws Exception { + + if (originalConf != null) { + System.setProperty("atlas.conf", originalConf); + } + applicationContext = null; + authProvider = null; + } +} diff --git a/webapp/src/test/java/org/apache/atlas/web/security/UserDaoTest.java b/webapp/src/test/java/org/apache/atlas/web/security/UserDaoTest.java new file mode 100644 index 0000000..5b5c70e --- /dev/null +++ b/webapp/src/test/java/org/apache/atlas/web/security/UserDaoTest.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliRance 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.apache.atlas.web.security; + +import java.util.Properties; + +import org.apache.atlas.web.dao.UserDao; +import org.apache.atlas.web.model.User; +import org.junit.Assert; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.testng.annotations.Test; + +public class UserDaoTest { + + @Test + public void testUserDaowithValidUserLoginAndPassword() { + + Properties userLogins = new Properties(); + userLogins.put("admin", "admin123"); + + UserDao user = new UserDao(); + user.setUserLogins(userLogins); + User userBean = user.loadUserByUsername("admin"); + Assert.assertTrue(userBean.getPassword().equals("admin123")); + + } + + @Test + public void testUserDaowithInValidLogin() { + boolean hadException = false; + Properties userLogins = new Properties(); + userLogins.put("admin", "admin123"); + userLogins.put("test", "test123"); + + UserDao user = new UserDao(); + user.setUserLogins(userLogins); + try { + User userBean = user.loadUserByUsername("xyz"); + } catch (UsernameNotFoundException uex) { + hadException = true; + } + Assert.assertTrue(hadException); + } + +} \ No newline at end of file