blob: 12d8fd23870b9dd20c61ffaa58d12a0cf0f5576f [file] [log] [blame]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Testing Security 3</title>
<meta name="description" content="Apache TomEE">
<meta name="author" content="Apache TomEE">
<meta name="google-translate-customization" content="f36a520c08f4c9-0a04e86a9c075ce9-g265f3196f697cf8f-10">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate, max-age=0">
<!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<!-- Le styles -->
<link href="./../../resources/css/bootstrap.css" rel="stylesheet">
<link href="./../../resources/css/prettify.css" rel="stylesheet">
<!--link href="./../../resources/css/bootstrap-mods.css" rel="stylesheet"-->
<link href="./../../resources/css/main.css" rel="stylesheet">
<link href="./../../resources/font-awesome-4.6.3/css/font-awesome.min.css" rel="stylesheet">
<script type="text/javascript">
var t = encodeURIComponent(document.title.replace(/^\s+|\s+$/g,""));
var u = encodeURIComponent(""+document.URL);
function fbshare () {
window.open(
"http://www.facebook.com/sharer/sharer.php?u="+u,
'Share on Facebook',
'width=640,height=426');
};
function gpshare () {
window.open(
"https://plus.google.com/share?url="+u,
'Share on Google+',
'width=584,height=385');
};
function twshare () {
window.open(
"https://twitter.com/intent/tweet?url="+u+"&text="+t,
'Share on Twitter',
'width=800,height=526');
};
function pinshare () {
window.open("//www.pinterest.com/pin/create/button/?url="+u+"&media=http%3A%2F%2Ftomee.apache.org%2Fresources%2Fimages%2Ffeather-logo.png&description="+t,
'Share on Pinterest',
'width=800,height=526');
};
</script>
<!-- Le fav and touch icons -->
<link rel="shortcut icon" href="./../../favicon.ico">
<link rel="apple-touch-icon" href="./../../resources/images/apple-touch-icon.png">
<link rel="apple-touch-icon" sizes="72x72" href="./../../resources/images/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="114x114" href="./../../resources/images/apple-touch-icon-114x114.png">
<script src="./../../resources/js/prettify.js" type="text/javascript"></script>
<script src="./../../resources/js/jquery-latest.js"></script>
<script src="http://platform.twitter.com/widgets.js" type="text/javascript"></script>
<script src="./../../resources/js/common.js"></script>
<script src="./../../resources/js/prettyprint.js"></script>
<!--script src="//assets.pinterest.com/js/pinit.js" type="text/javascript" async></script//-->
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-2717626-1']);
_gaq.push(['_setDomainName', 'apache.org']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</head>
<body>
<div class="topbar" data-dropdown="dropdown">
<div class="fill">
<div class="container">
<a class="brand" href="./../../index.html">Apache TomEE</a>
<ul class="nav">
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
Apache
<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<!-- <li><a href="./../../misc/whoweare.html">Who we are?</a></li> -->
<!-- <li><a href="./../../misc/heritage.html">Heritage</a></li> -->
<li><a href="http://www.apache.org">Apache Home</a></li>
<!-- <li><a href="./../../misc/resources.html">Resources</a></li> -->
<li><a href="./../../misc/contact.html">Contact</a></li>
<li><a href="./../../misc/legal.html">Legal</a></li>
<li><a href="http://www.apache.org/foundation/sponsorship.html">Sponsorship</a></li>
<li><a href="http://www.apache.org/foundation/thanks.html">Thanks</a></li>
<li class="divider"/>
<li><a href="http://www.apache.org/security">Security</a></li>
</ul>
</li>
<li><a href="./../../index.html">Home</a></li>
<li><a href="./../../downloads.html">Downloads</a></li>
<li><a href="./../../documentation.html">Documentation</a></li>
<li><a href="./../../examples-trunk/index.html">Examples</a></li>
<li><a href="./../../support.html">Support</a></li>
<li><a href="./../../contribute.html">Contribute</a></li>
<li><a href="./../../security/index.html">Security</a></li>
</ul>
<!-- Google CSE Search Box Begins -->
<FORM class="pull-right" id="searchbox_010475492895890475512:_t4iqjrgx90" action="http://www.google.com/cse">
<INPUT type="hidden" name="cx" value="010475492895890475512:_t4iqjrgx90">
<INPUT type="hidden" name="cof" value="FORID:0">
<INPUT size="18" width="130" style="width:130px" name="q" type="text" placeholder="Search">
</FORM>
<!--<SCRIPT type="text/javascript" src="http://www.google.com/coop/cse/brand?form=searchbox_010475492895890475512:_t4iqjrgx90"></SCRIPT>-->
<!-- Google CSE Search Box Ends -->
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="span8">
<small><a href="./../../index.html">Home</a>&nbsp;&raquo&nbsp;<a href="./../../examples-trunk/">Examples Trunk</a>&nbsp;&raquo&nbsp;<a href="./../../examples-trunk/testing-security-3/">Testing Security 3</a></small><br>
</div>
<div class="span8">
</div>
</div>
&nbsp;
<div class="page-header">
<h1>Testing Security 3
<div style="float: right; position: relative; bottom: -10px; ">
<div id="google_translate_element"></div><script type="text/javascript">
function googleTranslateElementInit() {
new google.translate.TranslateElement({pageLanguage: 'en', layout: google.translate.TranslateElement.InlineLayout.SIMPLE}, 'google_translate_element');
}
</script><script type="text/javascript" src="//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>
<a onclick="javascript:gpshare()" class="gp-share sprite" title="Share on Google+">share [gp]</a>
<a onclick="javascript:fbshare()" class="fb-share sprite" title="Share on Facebook">share [fb]</a>
<a onclick="javascript:twshare()" class="tw-share sprite" title="Share on Twitter">share [tw]</a>
<a onclick="javascript:pinshare()" class="pin-share sprite" title="Share on Pinterest">share [pin]</a>
<a data-toggle="modal" href="#edit" class="edit-page" title="Contribute to this Page">contribute</a>
</div>
</h1>
</div>
<p><em>Help us document this example! Click the blue pencil icon in the upper right to edit this page.</em></p>
<h2>Movie</h2>
<pre><code>package org.superbiz.injection.secure;
import javax.persistence.Entity;
@Entity
public class Movie {
private String director;
private String title;
private int year;
public Movie() {
}
public Movie(String director, String title, int year) {
this.director = director;
this.title = title;
this.year = year;
}
public String getDirector() {
return director;
}
public void setDirector(String director) {
this.director = director;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
}
</code></pre>
<h2>Movies</h2>
<pre><code>package org.superbiz.injection.secure;
//START SNIPPET: code
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ejb.Stateful;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.Query;
import java.util.List;
@Stateful
public class Movies {
@PersistenceContext(unitName = "movie-unit", type = PersistenceContextType.EXTENDED)
private EntityManager entityManager;
@RolesAllowed({"Employee", "Manager"})
public void addMovie(Movie movie) throws Exception {
entityManager.persist(movie);
}
@RolesAllowed({"Manager"})
public void deleteMovie(Movie movie) throws Exception {
entityManager.remove(movie);
}
@PermitAll
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public List&lt;Movie&gt; getMovies() throws Exception {
Query query = entityManager.createQuery("SELECT m from Movie as m");
return query.getResultList();
}
}
</code></pre>
<h2>MyLoginProvider</h2>
<pre><code>package org.superbiz.injection.secure;
import org.apache.openejb.core.security.jaas.LoginProvider;
import javax.security.auth.login.FailedLoginException;
import java.util.Arrays;
import java.util.List;
public class MyLoginProvider implements LoginProvider {
@Override
public List&lt;String&gt; authenticate(String user, String password) throws FailedLoginException {
if ("paul".equals(user) &amp;&amp; "michelle".equals(password)) {
return Arrays.asList("Manager", "rockstar", "beatle");
}
if ("eddie".equals(user) &amp;&amp; "jump".equals(password)) {
return Arrays.asList("Employee", "rockstar", "vanhalen");
}
throw new FailedLoginException("Bad user or password!");
}
}
</code></pre>
<h2>org.apache.openejb.core.security.jaas.LoginProvider</h2>
<pre><code>org.superbiz.injection.secure.MyLoginProvider
</code></pre>
<h2>persistence.xml</h2>
<pre><code>&lt;persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0"&gt;
&lt;persistence-unit name="movie-unit"&gt;
&lt;jta-data-source&gt;movieDatabase&lt;/jta-data-source&gt;
&lt;non-jta-data-source&gt;movieDatabaseUnmanaged&lt;/non-jta-data-source&gt;
&lt;class&gt;org.superbiz.injection.secure.Movie&lt;/class&gt;
&lt;properties&gt;
&lt;property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)"/&gt;
&lt;/properties&gt;
&lt;/persistence-unit&gt;
&lt;/persistence&gt;
</code></pre>
<h2>MovieTest</h2>
<pre><code>package org.superbiz.injection.secure;
import junit.framework.TestCase;
import javax.ejb.EJB;
import javax.ejb.EJBAccessException;
import javax.ejb.embeddable.EJBContainer;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.List;
import java.util.Properties;
public class MovieTest extends TestCase {
@EJB
private Movies movies;
private Context getContext(String user, String pass) throws NamingException {
Properties p = new Properties();
p.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.core.LocalInitialContextFactory");
p.setProperty("openejb.authentication.realmName", "ServiceProviderLogin");
p.put(Context.SECURITY_PRINCIPAL, user);
p.put(Context.SECURITY_CREDENTIALS, pass);
return new InitialContext(p);
}
protected void setUp() throws Exception {
Properties p = new Properties();
p.put("movieDatabase", "new://Resource?type=DataSource");
p.put("movieDatabase.JdbcDriver", "org.hsqldb.jdbcDriver");
p.put("movieDatabase.JdbcUrl", "jdbc:hsqldb:mem:moviedb");
EJBContainer.createEJBContainer(p).getContext().bind("inject", this);
}
public void testAsManager() throws Exception {
final Context context = getContext("paul", "michelle");
try {
movies.addMovie(new Movie("Quentin Tarantino", "Reservoir Dogs", 1992));
movies.addMovie(new Movie("Joel Coen", "Fargo", 1996));
movies.addMovie(new Movie("Joel Coen", "The Big Lebowski", 1998));
List&lt;Movie&gt; list = movies.getMovies();
assertEquals("List.size()", 3, list.size());
for (Movie movie : list) {
movies.deleteMovie(movie);
}
assertEquals("Movies.getMovies()", 0, movies.getMovies().size());
} finally {
context.close();
}
}
public void testAsEmployee() throws Exception {
final Context context = getContext("eddie", "jump");
try {
movies.addMovie(new Movie("Quentin Tarantino", "Reservoir Dogs", 1992));
movies.addMovie(new Movie("Joel Coen", "Fargo", 1996));
movies.addMovie(new Movie("Joel Coen", "The Big Lebowski", 1998));
List&lt;Movie&gt; list = movies.getMovies();
assertEquals("List.size()", 3, list.size());
for (Movie movie : list) {
try {
movies.deleteMovie(movie);
fail("Employees should not be allowed to delete");
} catch (EJBAccessException e) {
// Good, Employees cannot delete things
}
}
// The list should still be three movies long
assertEquals("Movies.getMovies()", 3, movies.getMovies().size());
} finally {
context.close();
}
}
public void testUnauthenticated() throws Exception {
try {
movies.addMovie(new Movie("Quentin Tarantino", "Reservoir Dogs", 1992));
fail("Unauthenticated users should not be able to add movies");
} catch (EJBAccessException e) {
// Good, guests cannot add things
}
try {
movies.deleteMovie(null);
fail("Unauthenticated users should not be allowed to delete");
} catch (EJBAccessException e) {
// Good, Unauthenticated users cannot delete things
}
try {
// Read access should be allowed
List&lt;Movie&gt; list = movies.getMovies();
} catch (EJBAccessException e) {
fail("Read access should be allowed");
}
}
public void testLoginFailure() throws NamingException {
try {
getContext("eddie", "panama");
fail("supposed to have a login failure here");
} catch (javax.naming.AuthenticationException e) {
//expected
}
try {
getContext("jimmy", "foxylady");
fail("supposed to have a login failure here");
} catch (javax.naming.AuthenticationException e) {
//expected
}
}
}
</code></pre>
<h1>Running</h1>
<pre><code>-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running org.superbiz.injection.secure.MovieTest
INFO - ********************************************************************************
INFO - OpenEJB http://tomee.apache.org/
INFO - Startup: Fri Jul 20 08:42:53 EDT 2012
INFO - Copyright 1999-2012 (C) Apache OpenEJB Project, All Rights Reserved.
INFO - Version: 4.1.0
INFO - Build date: 20120720
INFO - Build time: 08:33
INFO - ********************************************************************************
INFO - openejb.home = /home/boto/dev/ws/openejb_trunk/openejb/examples/testing-security-3
INFO - openejb.base = /home/boto/dev/ws/openejb_trunk/openejb/examples/testing-security-3
INFO - Created new singletonService org.apache.openejb.cdi.ThreadSingletonServiceImpl@38ee6681
INFO - Succeeded in installing singleton service
INFO - Using 'javax.ejb.embeddable.EJBContainer=true'
INFO - Cannot find the configuration file [conf/openejb.xml]. Will attempt to create one for the beans deployed.
INFO - Configuring Service(id=Default Security Service, type=SecurityService, provider-id=Default Security Service)
INFO - Configuring Service(id=Default Transaction Manager, type=TransactionManager, provider-id=Default Transaction Manager)
INFO - Configuring Service(id=movieDatabase, type=Resource, provider-id=Default JDBC Database)
INFO - Creating TransactionManager(id=Default Transaction Manager)
INFO - Creating SecurityService(id=Default Security Service)
INFO - Creating Resource(id=movieDatabase)
INFO - Beginning load: /home/boto/dev/ws/openejb_trunk/openejb/examples/testing-security-3/target/classes
INFO - Configuring enterprise application: /home/boto/dev/ws/openejb_trunk/openejb/examples/testing-security-3
INFO - Auto-deploying ejb Movies: EjbDeployment(deployment-id=Movies)
INFO - Configuring Service(id=Default Stateful Container, type=Container, provider-id=Default Stateful Container)
INFO - Auto-creating a container for bean Movies: Container(type=STATEFUL, id=Default Stateful Container)
INFO - Creating Container(id=Default Stateful Container)
INFO - Using directory /tmp for stateful session passivation
INFO - Configuring Service(id=Default Managed Container, type=Container, provider-id=Default Managed Container)
INFO - Auto-creating a container for bean org.superbiz.injection.secure.MovieTest: Container(type=MANAGED, id=Default Managed Container)
INFO - Creating Container(id=Default Managed Container)
INFO - Using directory /tmp for stateful session passivation
INFO - Configuring PersistenceUnit(name=movie-unit)
INFO - Auto-creating a Resource with id 'movieDatabaseNonJta' of type 'DataSource for 'movie-unit'.
INFO - Configuring Service(id=movieDatabaseNonJta, type=Resource, provider-id=movieDatabase)
INFO - Creating Resource(id=movieDatabaseNonJta)
INFO - Adjusting PersistenceUnit movie-unit &lt;non-jta-data-source&gt; to Resource ID 'movieDatabaseNonJta' from 'movieDatabaseUnmanaged'
INFO - Enterprise application "/home/boto/dev/ws/openejb_trunk/openejb/examples/testing-security-3" loaded.
INFO - Assembling app: /home/boto/dev/ws/openejb_trunk/openejb/examples/testing-security-3
SEVERE - JAVA AGENT NOT INSTALLED. The JPA Persistence Provider requested installation of a ClassFileTransformer which requires a JavaAgent. See http://tomee.apache.org/3.0/javaagent.html
INFO - PersistenceUnit(name=movie-unit, provider=org.apache.openjpa.persistence.PersistenceProviderImpl) - provider time 268ms
INFO - Jndi(name="java:global/testing-security-3/Movies!org.superbiz.injection.secure.Movies")
INFO - Jndi(name="java:global/testing-security-3/Movies")
INFO - Existing thread singleton service in SystemInstance() org.apache.openejb.cdi.ThreadSingletonServiceImpl@38ee6681
INFO - OpenWebBeans Container is starting...
INFO - Adding OpenWebBeansPlugin : [CdiPlugin]
INFO - All injection points are validated successfully.
INFO - OpenWebBeans Container has started, it took 170 ms.
INFO - Created Ejb(deployment-id=Movies, ejb-name=Movies, container=Default Stateful Container)
INFO - Started Ejb(deployment-id=Movies, ejb-name=Movies, container=Default Stateful Container)
INFO - Deployed Application(path=/home/boto/dev/ws/openejb_trunk/openejb/examples/testing-security-3)
20-Jul-2012 8:42:55 AM null openjpa.Runtime
INFO: Starting OpenJPA 2.2.0
20-Jul-2012 8:42:56 AM null openjpa.jdbc.JDBC
INFO: Using dictionary class "org.apache.openjpa.jdbc.sql.HSQLDictionary" (HSQL Database Engine 2.2.8 ,HSQL Database Engine Driver 2.2.8).
20-Jul-2012 8:42:57 AM null openjpa.Enhance
INFO: Creating subclass and redefining methods for "[class org.superbiz.injection.secure.Movie]". This means that your application will be less efficient than it would if you ran the OpenJPA enhancer.
INFO - Logging in
INFO - Logging out
INFO - EJBContainer already initialized. Call ejbContainer.close() to allow reinitialization
INFO - Logging in
INFO - Logging out
INFO - EJBContainer already initialized. Call ejbContainer.close() to allow reinitialization
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 6.069 sec
Results :
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
</code></pre>
<div class="page-header">&nbsp;</div>
<h4>APIs Used</h4>
<ul><li><a href="http://docs.oracle.com/javaee/6/api/javax/annotation/security/PermitAll.html">javax.annotation.security.PermitAll</a></li>
<li><a href="http://docs.oracle.com/javaee/6/api/javax/annotation/security/RolesAllowed.html">javax.annotation.security.RolesAllowed</a></li>
<li><a href="http://docs.oracle.com/javaee/6/api/javax/ejb/EJB.html">javax.ejb.EJB</a></li>
<li><a href="http://docs.oracle.com/javaee/6/api/javax/ejb/EJBAccessException.html">javax.ejb.EJBAccessException</a></li>
<li><a href="http://docs.oracle.com/javaee/6/api/javax/ejb/Stateful.html">javax.ejb.Stateful</a></li>
<li><a href="http://docs.oracle.com/javaee/6/api/javax/ejb/TransactionAttribute.html">javax.ejb.TransactionAttribute</a></li>
<li><a href="http://docs.oracle.com/javaee/6/api/javax/ejb/TransactionAttributeType.html">javax.ejb.TransactionAttributeType</a></li>
<li><a href="http://docs.oracle.com/javaee/6/api/javax/ejb/embeddable/EJBContainer.html">javax.ejb.embeddable.EJBContainer</a></li>
<li><a href="http://docs.oracle.com/javaee/6/api/javax/naming/Context.html">javax.naming.Context</a></li>
<li><a href="http://docs.oracle.com/javaee/6/api/javax/naming/InitialContext.html">javax.naming.InitialContext</a></li>
<li><a href="http://docs.oracle.com/javaee/6/api/javax/naming/NamingException.html">javax.naming.NamingException</a></li>
<li><a href="http://docs.oracle.com/javaee/6/api/javax/persistence/Entity.html">javax.persistence.Entity</a></li>
<li><a href="http://docs.oracle.com/javaee/6/api/javax/persistence/EntityManager.html">javax.persistence.EntityManager</a></li>
<li><a href="http://docs.oracle.com/javaee/6/api/javax/persistence/PersistenceContext.html">javax.persistence.PersistenceContext</a></li>
<li><a href="http://docs.oracle.com/javaee/6/api/javax/persistence/PersistenceContextType.html">javax.persistence.PersistenceContextType</a></li>
<li><a href="http://docs.oracle.com/javaee/6/api/javax/persistence/Query.html">javax.persistence.Query</a></li>
<li><a href="http://docs.oracle.com/javaee/6/api/javax/security/auth/login/FailedLoginException.html">javax.security.auth.login.FailedLoginException</a></li>
</ul>
<h3>Source</h3>
<ul>
<li>Apache <a href="http://svn.apache.org/repos/asf/tomee/tomee/trunk/examples/testing-security-3">testing-security-3</a></li>
<li>Github <a href="https://github.com/apache/tomee/tree/trunk/examples/testing-security-3">testing-security-3</a></li>
</ul>
<pre>
svn co http://svn.apache.org/repos/asf/tomee/tomee/trunk/examples/testing-security-3
cd testing-security-3
mvn clean install
</pre>
<div id="edit" class="modal hide fade in" style="display: none; ">
<div class="modal-header">
<a class="close" data-dismiss="modal">x</a>
<h3>Thank you for contributing to the documentation!</h3>
</div>
<div class="modal-body">
<h4>Any help with the documentation is greatly appreciated.</h4>
<p>All edits are reviewed before going live, so feel free to do much more than fix typos or links. If you see a page that could benefit from an entire rewrite, we'd be thrilled to review it. Don't be surprised if we like it so much we ask you for help with other pages :)</p>
<small>NOTICE: unless indicated otherwise on the pages in question, all editable content available from apache.org is presumed to be licensed under the Apache License (AL) version 2.0 and hence all submissions to apache.org treated as formal Contributions under the license terms.</small>
<!--[if gt IE 6]>
<h4>Internet Explorer Users</h4>
<p>If you are not an Apache committer, click the Yes link and enter a <i>anonymous</i> for the username and leave the password empty</p>
<![endif]-->
</div>
<div class="modal-footer">
Do you have an Apache ID?
<a href="javascript:void(location.href='https://cms.apache.org/redirect?uri='+escape(location.href))" class="btn">Yes</a>
<a href="javascript:void(location.href='https://anonymous:@cms.apache.org/redirect?uri='+escape(location.href))" class="btn">No</a>
</div>
</div>
<script src="./../../resources/js/bootstrap-modal.js"></script>
<footer>
<p>Copyright &copy; 1999-2016 The Apache Software Foundation, Licensed under the Apache License, Version 2.0.
Apache TomEE, TomEE, Apache, the Apache feather logo, and the Apache TomEE project logo are trademarks of The Apache Software Foundation.
All other marks mentioned may be trademarks or registered trademarks of their respective owners.</p>
</footer>
</div> <!-- /container -->
<!-- Javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="./../../resources/js/bootstrap-dropdown.js"></script>
</body>
</html>