diff --git a/spring-security-oauth/.project b/spring-security-oauth/.project new file mode 100644 index 0000000000..fe6e295165 --- /dev/null +++ b/spring-security-oauth/.project @@ -0,0 +1,17 @@ + + + spring-security-oauth + + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + + diff --git a/spring-security-oauth/pom.xml b/spring-security-oauth/pom.xml new file mode 100644 index 0000000000..0531270ea4 --- /dev/null +++ b/spring-security-oauth/pom.xml @@ -0,0 +1,103 @@ + + 4.0.0 + org.baeldung + spring-security-oauth + 1.0.0-SNAPSHOT + + spring-security-oauth + pom + + + org.springframework.boot + spring-boot-starter-parent + 1.2.7.RELEASE + + + + spring-security-oauth-server + spring-security-oauth-resource + spring-security-oauth-ui + + + + spring-security-oauth + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + 1.8 + 1.8 + + + + + org.apache.maven.plugins + maven-war-plugin + ${maven-war-plugin.version} + + false + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + true + + **/*IntegrationTest.java + **/*LiveTest.java + + + + + + + + + + + + + + + 4.1.7.RELEASE + 3.2.8.RELEASE + 2.0.7.RELEASE + + + + 2.5.1 + + + 1.7.12 + 1.1.3 + + + 18.0 + 3.3.2 + + + 1.3 + 4.11 + 1.10.19 + + 4.4 + 4.4 + + 2.4.0 + + + 3.3 + 2.6 + 2.19 + 1.4.16 + + + + \ No newline at end of file diff --git a/spring-security-oauth/spring-security-oauth-resource/.classpath b/spring-security-oauth/spring-security-oauth-resource/.classpath new file mode 100644 index 0000000000..5c3ac53820 --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-resource/.classpath @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-security-oauth/spring-security-oauth-resource/.project b/spring-security-oauth/spring-security-oauth-resource/.project new file mode 100644 index 0000000000..c3a285960b --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-resource/.project @@ -0,0 +1,48 @@ + + + spring-security-oauth-resource + + + + + + org.eclipse.wst.jsdt.core.javascriptValidator + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.springframework.ide.eclipse.core.springbuilder + + + + + org.eclipse.wst.validation.validationbuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.wst.common.modulecore.ModuleCoreNature + org.springframework.ide.eclipse.core.springnature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.eclipse.wst.common.project.facet.core.nature + org.eclipse.wst.jsdt.core.jsNature + + diff --git a/spring-security-oauth/spring-security-oauth-resource/pom.xml b/spring-security-oauth/spring-security-oauth-resource/pom.xml new file mode 100644 index 0000000000..88cc87c491 --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-resource/pom.xml @@ -0,0 +1,48 @@ + + 4.0.0 + spring-security-oauth-resource + spring-security-oauth-resource + war + + + org.baeldung + spring-security-oauth + 1.0.0-SNAPSHOT + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework + spring-jdbc + + + + mysql + mysql-connector-java + runtime + + + + org.springframework.security.oauth + spring-security-oauth2 + ${oauth.version} + + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + + + + + + \ No newline at end of file diff --git a/spring-security-oauth/spring-security-oauth-resource/src/main/java/org/baeldung/config/OAuth2ResourceConfig.java b/spring-security-oauth/spring-security-oauth-resource/src/main/java/org/baeldung/config/OAuth2ResourceConfig.java new file mode 100644 index 0000000000..e4f0fe5578 --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-resource/src/main/java/org/baeldung/config/OAuth2ResourceConfig.java @@ -0,0 +1,36 @@ +package org.baeldung.config; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.core.env.Environment; +import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; + +@Configuration +@PropertySource({ "classpath:persistence.properties" }) +public class OAuth2ResourceConfig { + + @Autowired + private Environment env; + + @Bean + public DataSource dataSource() { + final DriverManagerDataSource dataSource = new DriverManagerDataSource(); + dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName")); + dataSource.setUrl(env.getProperty("jdbc.url")); + dataSource.setUsername(env.getProperty("jdbc.user")); + dataSource.setPassword(env.getProperty("jdbc.pass")); + return dataSource; + } + + @Bean + public TokenStore tokenStore() { + return new JdbcTokenStore(dataSource()); + } + +} diff --git a/spring-security-oauth/spring-security-oauth-resource/src/main/java/org/baeldung/config/ResourceApplication.java b/spring-security-oauth/spring-security-oauth-resource/src/main/java/org/baeldung/config/ResourceApplication.java new file mode 100644 index 0000000000..0ac197cd47 --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-resource/src/main/java/org/baeldung/config/ResourceApplication.java @@ -0,0 +1,14 @@ +package org.baeldung.config; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.web.SpringBootServletInitializer; + +@SpringBootApplication +public class ResourceApplication extends SpringBootServletInitializer { + + public static void main(String[] args) { + SpringApplication.run(ResourceApplication.class, args); + } + +} \ No newline at end of file diff --git a/spring-security-oauth/spring-security-oauth-resource/src/main/java/org/baeldung/config/ResourceSecurityConfig.java b/spring-security-oauth/spring-security-oauth-resource/src/main/java/org/baeldung/config/ResourceSecurityConfig.java new file mode 100644 index 0000000000..9ad2131d1e --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-resource/src/main/java/org/baeldung/config/ResourceSecurityConfig.java @@ -0,0 +1,17 @@ +package org.baeldung.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; +import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler; + +@Configuration +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class ResourceSecurityConfig extends GlobalMethodSecurityConfiguration { + + @Override + protected MethodSecurityExpressionHandler createExpressionHandler() { + return new OAuth2MethodSecurityExpressionHandler(); + } +} diff --git a/spring-security-oauth/spring-security-oauth-resource/src/main/java/org/baeldung/config/ResourceWebConfig.java b/spring-security-oauth/spring-security-oauth-resource/src/main/java/org/baeldung/config/ResourceWebConfig.java new file mode 100644 index 0000000000..ed0bb9f81b --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-resource/src/main/java/org/baeldung/config/ResourceWebConfig.java @@ -0,0 +1,13 @@ +package org.baeldung.config; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +@Configuration +@EnableWebMvc +@ComponentScan({ "org.baeldung.web.controller" }) +public class ResourceWebConfig extends WebMvcConfigurerAdapter { + +} diff --git a/spring-security-oauth/spring-security-oauth-resource/src/main/java/org/baeldung/web/controller/FooController.java b/spring-security-oauth/spring-security-oauth-resource/src/main/java/org/baeldung/web/controller/FooController.java new file mode 100644 index 0000000000..d8f830a76c --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-resource/src/main/java/org/baeldung/web/controller/FooController.java @@ -0,0 +1,31 @@ +package org.baeldung.web.controller; + +import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; +import static org.apache.commons.lang3.RandomStringUtils.randomNumeric; + +import org.baeldung.web.dto.Foo; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +@EnableResourceServer +public class FooController { + + public FooController() { + super(); + } + + // API - read + @PreAuthorize("#oauth2.hasScope('read')") + @RequestMapping(method = RequestMethod.GET, value = "/foos/{id}") + @ResponseBody + public Foo findById(@PathVariable final long id) { + return new Foo(Long.parseLong(randomNumeric(2)), randomAlphabetic(4)); + } + +} diff --git a/spring-security-oauth/spring-security-oauth-resource/src/main/java/org/baeldung/web/dto/Foo.java b/spring-security-oauth/spring-security-oauth-resource/src/main/java/org/baeldung/web/dto/Foo.java new file mode 100644 index 0000000000..9d26618e7f --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-resource/src/main/java/org/baeldung/web/dto/Foo.java @@ -0,0 +1,36 @@ +package org.baeldung.web.dto; + +public class Foo { + private long id; + private String name; + + public Foo() { + super(); + } + + public Foo(final long id, final String name) { + super(); + + this.id = id; + this.name = name; + } + + // + + public long getId() { + return id; + } + + public void setId(final long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + +} \ No newline at end of file diff --git a/spring-security-oauth/spring-security-oauth-resource/src/main/resources/application.properties b/spring-security-oauth/spring-security-oauth-resource/src/main/resources/application.properties new file mode 100644 index 0000000000..294f69c54e --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-resource/src/main/resources/application.properties @@ -0,0 +1,2 @@ +#server.contextPath=/resource +#server.port=8081 \ No newline at end of file diff --git a/spring-security-oauth/spring-security-oauth-resource/src/main/resources/persistence.properties b/spring-security-oauth/spring-security-oauth-resource/src/main/resources/persistence.properties new file mode 100644 index 0000000000..b975b10e9f --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-resource/src/main/resources/persistence.properties @@ -0,0 +1,6 @@ +################### DataSource Configuration ########################## +jdbc.driverClassName=com.mysql.jdbc.Driver +jdbc.url=jdbc:mysql://localhost:3306/oauth2?createDatabaseIfNotExist=true +jdbc.user=tutorialuser +jdbc.pass=tutorialmy5ql + diff --git a/spring-security-oauth/spring-security-oauth-server/.classpath b/spring-security-oauth/spring-security-oauth-server/.classpath new file mode 100644 index 0000000000..5c3ac53820 --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-server/.classpath @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-security-oauth/spring-security-oauth-server/.project b/spring-security-oauth/spring-security-oauth-server/.project new file mode 100644 index 0000000000..a66e7f1009 --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-server/.project @@ -0,0 +1,48 @@ + + + spring-security-oauth-server + + + + + + org.eclipse.wst.jsdt.core.javascriptValidator + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.springframework.ide.eclipse.core.springbuilder + + + + + org.eclipse.wst.validation.validationbuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.wst.common.modulecore.ModuleCoreNature + org.springframework.ide.eclipse.core.springnature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.eclipse.wst.common.project.facet.core.nature + org.eclipse.wst.jsdt.core.jsNature + + diff --git a/spring-security-oauth/spring-security-oauth-server/pom.xml b/spring-security-oauth/spring-security-oauth-server/pom.xml new file mode 100644 index 0000000000..211b9a240c --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-server/pom.xml @@ -0,0 +1,57 @@ + + 4.0.0 + spring-security-oauth-server + + spring-security-oauth-server + war + + + org.baeldung + spring-security-oauth + 1.0.0-SNAPSHOT + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework + spring-jdbc + + + + mysql + mysql-connector-java + runtime + + + + + + + org.springframework.security.oauth + spring-security-oauth2 + ${oauth.version} + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + \ No newline at end of file diff --git a/spring-security-oauth/spring-security-oauth-server/src/main/java/org/baeldung/config/AuthServerOAuth2Config.java b/spring-security-oauth/spring-security-oauth-server/src/main/java/org/baeldung/config/AuthServerOAuth2Config.java new file mode 100644 index 0000000000..572fcc5efa --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-server/src/main/java/org/baeldung/config/AuthServerOAuth2Config.java @@ -0,0 +1,88 @@ +package org.baeldung.config; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.core.env.Environment; +import org.springframework.core.io.Resource; +import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.jdbc.datasource.init.DataSourceInitializer; +import org.springframework.jdbc.datasource.init.DatabasePopulator; +import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; + +@Configuration +@PropertySource({ "classpath:persistence.properties" }) +@EnableAuthorizationServer +public class AuthServerOAuth2Config extends AuthorizationServerConfigurerAdapter { + + @Autowired + private Environment env; + + @Autowired + private AuthenticationManager authenticationManager; + + @Value("classpath:schema.sql") + private Resource schemaScript; + + @Override + public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { + oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()"); + } + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // @formatter:off + clients.jdbc(dataSource()) + .withClient("clientId") + .authorizedGrantTypes("implicit") + .scopes("read","write") + .autoApprove(true); + // @formatter:on + } + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager); + } + + @Bean + public DataSourceInitializer dataSourceInitializer(final DataSource dataSource) { + final DataSourceInitializer initializer = new DataSourceInitializer(); + initializer.setDataSource(dataSource); + initializer.setDatabasePopulator(databasePopulator()); + return initializer; + } + + private DatabasePopulator databasePopulator() { + final ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); + populator.addScript(schemaScript); + return populator; + } + + @Bean + public DataSource dataSource() { + final DriverManagerDataSource dataSource = new DriverManagerDataSource(); + dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName")); + dataSource.setUrl(env.getProperty("jdbc.url")); + dataSource.setUsername(env.getProperty("jdbc.user")); + dataSource.setPassword(env.getProperty("jdbc.pass")); + return dataSource; + } + + @Bean + public TokenStore tokenStore() { + return new JdbcTokenStore(dataSource()); + } +} diff --git a/spring-security-oauth/spring-security-oauth-server/src/main/java/org/baeldung/config/ServerApplication.java b/spring-security-oauth/spring-security-oauth-server/src/main/java/org/baeldung/config/ServerApplication.java new file mode 100644 index 0000000000..4b1ff2c7ad --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-server/src/main/java/org/baeldung/config/ServerApplication.java @@ -0,0 +1,14 @@ +package org.baeldung.config; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.web.SpringBootServletInitializer; + +@SpringBootApplication +public class ServerApplication extends SpringBootServletInitializer { + + public static void main(String[] args) { + SpringApplication.run(ServerApplication.class, args); + } + +} \ No newline at end of file diff --git a/spring-security-oauth/spring-security-oauth-server/src/main/java/org/baeldung/config/ServerSecurityConfig.java b/spring-security-oauth/spring-security-oauth-server/src/main/java/org/baeldung/config/ServerSecurityConfig.java new file mode 100644 index 0000000000..ec9bdfe0ce --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-server/src/main/java/org/baeldung/config/ServerSecurityConfig.java @@ -0,0 +1,27 @@ +package org.baeldung.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +public class ServerSecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(final AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication().withUser("john").password("123").roles("USER"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests() + .antMatchers("/login").permitAll() + .anyRequest().authenticated() + .and() + .formLogin().permitAll(); + // @formatter:on + } +} diff --git a/spring-security-oauth/spring-security-oauth-server/src/main/resources/application.properties b/spring-security-oauth/spring-security-oauth-server/src/main/resources/application.properties new file mode 100644 index 0000000000..b7465f46b4 --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-server/src/main/resources/application.properties @@ -0,0 +1 @@ +#server.contextPath=/server \ No newline at end of file diff --git a/spring-security-oauth/spring-security-oauth-server/src/main/resources/persistence.properties b/spring-security-oauth/spring-security-oauth-server/src/main/resources/persistence.properties new file mode 100644 index 0000000000..b975b10e9f --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-server/src/main/resources/persistence.properties @@ -0,0 +1,6 @@ +################### DataSource Configuration ########################## +jdbc.driverClassName=com.mysql.jdbc.Driver +jdbc.url=jdbc:mysql://localhost:3306/oauth2?createDatabaseIfNotExist=true +jdbc.user=tutorialuser +jdbc.pass=tutorialmy5ql + diff --git a/spring-security-oauth/spring-security-oauth-server/src/main/resources/schema.sql b/spring-security-oauth/spring-security-oauth-server/src/main/resources/schema.sql new file mode 100644 index 0000000000..02c4d824b7 --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-server/src/main/resources/schema.sql @@ -0,0 +1,66 @@ +-- used in tests that use HSQL +create table if not exists oauth_client_details ( + client_id VARCHAR(256) PRIMARY KEY, + resource_ids VARCHAR(256), + client_secret VARCHAR(256), + scope VARCHAR(256), + authorized_grant_types VARCHAR(256), + web_server_redirect_uri VARCHAR(256), + authorities VARCHAR(256), + access_token_validity INTEGER, + refresh_token_validity INTEGER, + additional_information VARCHAR(4096), + autoapprove VARCHAR(256) +); + +create table if not exists oauth_client_token ( + token_id VARCHAR(256), + token LONG VARBINARY, + authentication_id VARCHAR(256) PRIMARY KEY, + user_name VARCHAR(256), + client_id VARCHAR(256) +); + +create table if not exists oauth_access_token ( + token_id VARCHAR(256), + token LONG VARBINARY, + authentication_id VARCHAR(256) PRIMARY KEY, + user_name VARCHAR(256), + client_id VARCHAR(256), + authentication LONG VARBINARY, + refresh_token VARCHAR(256) +); + +create table if not exists oauth_refresh_token ( + token_id VARCHAR(256), + token LONG VARBINARY, + authentication LONG VARBINARY +); + +create table if not exists oauth_code ( + code VARCHAR(256), authentication LONG VARBINARY +); + +create table if not exists oauth_approvals ( + userId VARCHAR(256), + clientId VARCHAR(256), + scope VARCHAR(256), + status VARCHAR(10), + expiresAt TIMESTAMP, + lastModifiedAt TIMESTAMP +); + +-- customized oauth_client_details table +create table if not exists ClientDetails ( + appId VARCHAR(256) PRIMARY KEY, + resourceIds VARCHAR(256), + appSecret VARCHAR(256), + scope VARCHAR(256), + grantTypes VARCHAR(256), + redirectUrl VARCHAR(256), + authorities VARCHAR(256), + access_token_validity INTEGER, + refresh_token_validity INTEGER, + additionalInformation VARCHAR(4096), + autoApproveScopes VARCHAR(256) +); diff --git a/spring-security-oauth/spring-security-oauth-ui/.classpath b/spring-security-oauth/spring-security-oauth-ui/.classpath new file mode 100644 index 0000000000..0cad5db2d0 --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-ui/.classpath @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-security-oauth/spring-security-oauth-ui/.project b/spring-security-oauth/spring-security-oauth-ui/.project new file mode 100644 index 0000000000..7ff5398a88 --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-ui/.project @@ -0,0 +1,48 @@ + + + spring-security-oauth-ui + + + + + + org.eclipse.wst.jsdt.core.javascriptValidator + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.springframework.ide.eclipse.core.springbuilder + + + + + org.eclipse.wst.validation.validationbuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.wst.common.modulecore.ModuleCoreNature + org.springframework.ide.eclipse.core.springnature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.eclipse.wst.common.project.facet.core.nature + org.eclipse.wst.jsdt.core.jsNature + + diff --git a/spring-security-oauth/spring-security-oauth-ui/pom.xml b/spring-security-oauth/spring-security-oauth-ui/pom.xml new file mode 100644 index 0000000000..a9b25f75c8 --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-ui/pom.xml @@ -0,0 +1,29 @@ + + 4.0.0 + spring-security-oauth-ui + + spring-security-oauth-ui + war + + + org.baeldung + spring-security-oauth + 1.0.0-SNAPSHOT + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + + + \ No newline at end of file diff --git a/spring-security-oauth/spring-security-oauth-ui/src/main/java/org/baeldung/config/UiApplication.java b/spring-security-oauth/spring-security-oauth-ui/src/main/java/org/baeldung/config/UiApplication.java new file mode 100644 index 0000000000..8f491516aa --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-ui/src/main/java/org/baeldung/config/UiApplication.java @@ -0,0 +1,13 @@ +package org.baeldung.config; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.web.SpringBootServletInitializer; + +@SpringBootApplication +public class UiApplication extends SpringBootServletInitializer { + + public static void main(String[] args) { + SpringApplication.run(UiApplication.class, args); + } +} \ No newline at end of file diff --git a/spring-security-oauth/spring-security-oauth-ui/src/main/java/org/baeldung/config/UiWebConfig.java b/spring-security-oauth/spring-security-oauth-ui/src/main/java/org/baeldung/config/UiWebConfig.java new file mode 100644 index 0000000000..2ee0585615 --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-ui/src/main/java/org/baeldung/config/UiWebConfig.java @@ -0,0 +1,38 @@ +package org.baeldung.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +@Configuration +@EnableWebMvc +public class UiWebConfig extends WebMvcConfigurerAdapter { + + @Bean + public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } + + @Override + public void configureDefaultServletHandling(final DefaultServletHandlerConfigurer configurer) { + configurer.enable(); + } + + @Override + public void addViewControllers(final ViewControllerRegistry registry) { + super.addViewControllers(registry); + registry.addViewController("/index"); + registry.addViewController("/oauthTemp"); + } + + @Override + public void addResourceHandlers(final ResourceHandlerRegistry registry) { + registry.addResourceHandler("/resources/**").addResourceLocations("/resources/"); + } + +} \ No newline at end of file diff --git a/spring-security-oauth/spring-security-oauth-ui/src/main/resources/application.properties b/spring-security-oauth/spring-security-oauth-ui/src/main/resources/application.properties new file mode 100644 index 0000000000..84ffdbb9ad --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-ui/src/main/resources/application.properties @@ -0,0 +1,2 @@ +#server.context-path=/ui +#server.port=8080 \ No newline at end of file diff --git a/spring-security-oauth/spring-security-oauth-ui/src/main/resources/oauth-ng.js b/spring-security-oauth/spring-security-oauth-ui/src/main/resources/oauth-ng.js new file mode 100644 index 0000000000..333070b935 --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-ui/src/main/resources/oauth-ng.js @@ -0,0 +1,539 @@ +/* oauth-ng - v0.4.2 - 2015-08-27 */ + +'use strict'; + +// App libraries +angular.module('oauth', [ + 'oauth.directive', // login directive + 'oauth.accessToken', // access token service + 'oauth.endpoint', // oauth endpoint service + 'oauth.profile', // profile model + 'oauth.storage', // storage + 'oauth.interceptor', // bearer token interceptor + 'oauth.configuration' // token appender +]) + .config(['$locationProvider','$httpProvider', + function($locationProvider, $httpProvider) { + $httpProvider.interceptors.push('ExpiredInterceptor'); + }]); + +'use strict'; + +var accessTokenService = angular.module('oauth.accessToken', []); + +accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', '$interval', function(Storage, $rootScope, $location, $interval){ + + var service = { + token: null + }, + oAuth2HashTokens = [ //per http://tools.ietf.org/html/rfc6749#section-4.2.2 + 'access_token', 'token_type', 'expires_in', 'scope', 'state', + 'error','error_description' + ]; + + /** + * Returns the access token. + */ + service.get = function(){ + return this.token; + }; + + /** + * Sets and returns the access token. It tries (in order) the following strategies: + * - takes the token from the fragment URI + * - takes the token from the sessionStorage + */ + service.set = function(){ + this.setTokenFromString($location.hash()); + + //If hash is present in URL always use it, cuz its coming from oAuth2 provider redirect + if(null === service.token){ + setTokenFromSession(); + } + + return this.token; + }; + + /** + * Delete the access token and remove the session. + * @returns {null} + */ + service.destroy = function(){ + Storage.delete('token'); + this.token = null; + return this.token; + }; + + /** + * Tells if the access token is expired. + */ + service.expired = function(){ + return (this.token && this.token.expires_at && new Date(this.token.expires_at) < new Date()); + }; + + /** + * Get the access token from a string and save it + * @param hash + */ + service.setTokenFromString = function(hash){ + var params = getTokenFromString(hash); + + if(params){ + removeFragment(); + setToken(params); + setExpiresAt(); + // We have to save it again to make sure expires_at is set + // and the expiry event is set up properly + setToken(this.token); + $rootScope.$broadcast('oauth:login', service.token); + } + }; + + /* * * * * * * * * * + * PRIVATE METHODS * + * * * * * * * * * */ + + /** + * Set the access token from the sessionStorage. + */ + var setTokenFromSession = function(){ + var params = Storage.get('token'); + if (params) { + setToken(params); + } + }; + + /** + * Set the access token. + * + * @param params + * @returns {*|{}} + */ + var setToken = function(params){ + service.token = service.token || {}; // init the token + angular.extend(service.token, params); // set the access token params + setTokenInSession(); // save the token into the session + setExpiresAtEvent(); // event to fire when the token expires + + return service.token; + }; + + /** + * Parse the fragment URI and return an object + * @param hash + * @returns {{}} + */ + var getTokenFromString = function(hash){ + var params = {}, + regex = /([^&=]+)=([^&]*)/g, + m; + + while ((m = regex.exec(hash)) !== null) { + params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); + } + + if(params.access_token || params.error){ + return params; + } + }; + + /** + * Save the access token into the session + */ + var setTokenInSession = function(){ + Storage.set('token', service.token); + }; + + /** + * Set the access token expiration date (useful for refresh logics) + */ + var setExpiresAt = function(){ + if (!service.token) { + return; + } + if(typeof(service.token.expires_in) !== 'undefined' && service.token.expires_in !== null) { + var expires_at = new Date(); + expires_at.setSeconds(expires_at.getSeconds() + parseInt(service.token.expires_in)-60); // 60 seconds less to secure browser and response latency + service.token.expires_at = expires_at; + } + else { + service.token.expires_at = null; + } + }; + + + /** + * Set the timeout at which the expired event is fired + */ + var setExpiresAtEvent = function(){ + // Don't bother if there's no expires token + if (typeof(service.token.expires_at) === 'undefined' || service.token.expires_at === null) { + return; + } + var time = (new Date(service.token.expires_at))-(new Date()); + if(time && time > 0){ + $interval(function(){ + $rootScope.$broadcast('oauth:expired', service.token); + }, time, 1); + } + }; + + /** + * Remove the oAuth2 pieces from the hash fragment + */ + var removeFragment = function(){ + var curHash = $location.hash(); + angular.forEach(oAuth2HashTokens,function(hashKey){ + var re = new RegExp('&'+hashKey+'(=[^&]*)?|^'+hashKey+'(=[^&]*)?&?'); + curHash = curHash.replace(re,''); + }); + + $location.hash(curHash); + }; + + return service; + +}]); + +'use strict'; + +var endpointClient = angular.module('oauth.endpoint', []); + +endpointClient.factory('Endpoint', function() { + + var service = {}; + + /* + * Defines the authorization URL + */ + + service.set = function(configuration) { + this.config = configuration; + return this.get(); + }; + + /* + * Returns the authorization URL + */ + + service.get = function( overrides ) { + var params = angular.extend( {}, service.config, overrides); + var oAuthScope = (params.scope) ? encodeURIComponent(params.scope) : '', + state = (params.state) ? encodeURIComponent(params.state) : '', + authPathHasQuery = (params.authorizePath.indexOf('?') === -1) ? false : true, + appendChar = (authPathHasQuery) ? '&' : '?', //if authorizePath has ? already append OAuth2 params + responseType = (params.responseType) ? encodeURIComponent(params.responseType) : ''; + + var url = params.site + + params.authorizePath + + appendChar + 'response_type=' + responseType + '&' + + 'client_id=' + encodeURIComponent(params.clientId) + '&' + + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + + 'scope=' + oAuthScope + '&' + + 'state=' + state; + + if( params.nonce ) { + url = url + '&nonce=' + params.nonce; + } + return url; + }; + + /* + * Redirects the app to the authorization URL + */ + + service.redirect = function( overrides ) { + var targetLocation = this.get( overrides ); + window.location.replace(targetLocation); + }; + + return service; +}); + +'use strict'; + +var profileClient = angular.module('oauth.profile', []); + +profileClient.factory('Profile', ['$http', 'AccessToken', '$rootScope', function($http, AccessToken, $rootScope) { + var service = {}; + var profile; + + service.find = function(uri) { + var promise = $http.get(uri, { headers: headers() }); + promise.success(function(response) { + profile = response; + $rootScope.$broadcast('oauth:profile', profile); + }); + return promise; + }; + + service.get = function() { + return profile; + }; + + service.set = function(resource) { + profile = resource; + return profile; + }; + + var headers = function() { + return { Authorization: 'Bearer ' + AccessToken.get().access_token }; + }; + + return service; +}]); + +'use strict'; + +var storageService = angular.module('oauth.storage', ['ngStorage']); + +storageService.factory('Storage', ['$rootScope', '$sessionStorage', '$localStorage', function($rootScope, $sessionStorage, $localStorage){ + + var service = { + storage: $sessionStorage // By default + }; + + /** + * Deletes the item from storage, + * Returns the item's previous value + */ + service.delete = function (name) { + var stored = this.get(name); + delete this.storage[name]; + return stored; + }; + + /** + * Returns the item from storage + */ + service.get = function (name) { + return this.storage[name]; + }; + + /** + * Sets the item in storage to the value specified + * Returns the item's value + */ + service.set = function (name, value) { + this.storage[name] = value; + return this.get(name); + }; + + /** + * Change the storage service being used + */ + service.use = function (storage) { + if (storage === 'sessionStorage') { + this.storage = $sessionStorage; + } else if (storage === 'localStorage') { + this.storage = $localStorage; + } + }; + + return service; +}]); +'use strict'; + +var oauthConfigurationService = angular.module('oauth.configuration', []); + +oauthConfigurationService.provider('OAuthConfiguration', function() { + var _config = {}; + + this.init = function(config, httpProvider) { + _config.protectedResources = config.protectedResources || []; + httpProvider.interceptors.push('AuthInterceptor'); + }; + + this.$get = function() { + return { + getConfig: function() { + return _config; + } + }; + }; +}) +.factory('AuthInterceptor', function($q, $rootScope, OAuthConfiguration, AccessToken) { + return { + 'request': function(config) { + OAuthConfiguration.getConfig().protectedResources.forEach(function(resource) { + // If the url is one of the protected resources, we want to see if there's a token and then + // add the token if it exists. + if (config.url.indexOf(resource) > -1) { + var token = AccessToken.get(); + if (token) { + config.headers.Authorization = 'Bearer ' + token.access_token; + } + } + }); + + return config; + } + }; +}); +'use strict'; + +var interceptorService = angular.module('oauth.interceptor', []); + +interceptorService.factory('ExpiredInterceptor', ['Storage', '$rootScope', function (Storage, $rootScope) { + + var service = {}; + + service.request = function(config) { + var token = Storage.get('token'); + + if (token && expired(token)) { + $rootScope.$broadcast('oauth:expired', token); + } + + return config; + }; + + var expired = function(token) { + return (token && token.expires_at && new Date(token.expires_at) < new Date()); + }; + + return service; +}]); + +'use strict'; + +var directives = angular.module('oauth.directive', []); + +directives.directive('oauth', [ + 'AccessToken', + 'Endpoint', + 'Profile', + 'Storage', + '$location', + '$rootScope', + '$compile', + '$http', + '$templateCache', + function(AccessToken, Endpoint, Profile, Storage, $location, $rootScope, $compile, $http, $templateCache) { + + var definition = { + restrict: 'AE', + replace: true, + scope: { + site: '@', // (required) set the oauth server host (e.g. http://oauth.example.com) + clientId: '@', // (required) client id + redirectUri: '@', // (required) client redirect uri + responseType: '@', // (optional) response type, defaults to token (use 'token' for implicit flow and 'code' for authorization code flow + scope: '@', // (optional) scope + profileUri: '@', // (optional) user profile uri (e.g http://example.com/me) + template: '@', // (optional) template to render (e.g bower_components/oauth-ng/dist/views/templates/default.html) + text: '@', // (optional) login text + authorizePath: '@', // (optional) authorization url + state: '@', // (optional) An arbitrary unique string created by your app to guard against Cross-site Request Forgery + storage: '@' // (optional) Store token in 'sessionStorage' or 'localStorage', defaults to 'sessionStorage' + } + }; + + definition.link = function postLink(scope, element) { + scope.show = 'none'; + + scope.$watch('clientId', function() { + init(); + }); + + var init = function() { + initAttributes(); // sets defaults + Storage.use(scope.storage);// set storage + compile(); // compiles the desired layout + Endpoint.set(scope); // sets the oauth authorization url + AccessToken.set(scope); // sets the access token object (if existing, from fragment or session) + initProfile(scope); // gets the profile resource (if existing the access token) + initView(); // sets the view (logged in or out) + }; + + var initAttributes = function() { + scope.authorizePath = scope.authorizePath || '/oauth/authorize'; + scope.tokenPath = scope.tokenPath || '/oauth/token'; + scope.template = scope.template || 'bower_components/oauth-ng/dist/views/templates/default.html'; + scope.responseType = scope.responseType || 'token'; + scope.text = scope.text || 'Sign In'; + scope.state = scope.state || undefined; + scope.scope = scope.scope || undefined; + scope.storage = scope.storage || 'sessionStorage'; + }; + + var compile = function() { + $http.get(scope.template, { cache: $templateCache }).success(function(html) { + element.html(html); + $compile(element.contents())(scope); + }); + }; + + var initProfile = function(scope) { + var token = AccessToken.get(); + + if (token && token.access_token && scope.profileUri) { + Profile.find(scope.profileUri).success(function(response) { + scope.profile = response; + }); + } + }; + + var initView = function() { + var token = AccessToken.get(); + + if (!token) { + return loggedOut(); // without access token it's logged out + } + if (token.access_token) { + return authorized(); // if there is the access token we are done + } + if (token.error) { + return denied(); // if the request has been denied we fire the denied event + } + }; + + scope.login = function() { + Endpoint.redirect(); + }; + + scope.logout = function() { + AccessToken.destroy(scope); + $rootScope.$broadcast('oauth:logout'); + loggedOut(); + }; + + scope.$on('oauth:expired', function() { + AccessToken.destroy(scope); + scope.show = 'logged-out'; + }); + + // user is authorized + var authorized = function() { + $rootScope.$broadcast('oauth:authorized', AccessToken.get()); + scope.show = 'logged-in'; + }; + + // set the oauth directive to the logged-out status + var loggedOut = function() { + $rootScope.$broadcast('oauth:loggedOut'); + scope.show = 'logged-out'; + }; + + // set the oauth directive to the denied status + var denied = function() { + scope.show = 'denied'; + $rootScope.$broadcast('oauth:denied'); + }; + + // Updates the template at runtime + scope.$on('oauth:template:update', function(event, template) { + scope.template = template; + compile(scope); + }); + + // Hack to update the directive content on logout + // TODO think to a cleaner solution + scope.$on('$routeChangeSuccess', function () { + init(); + }); + }; + + return definition; + } +]); diff --git a/spring-security-oauth/spring-security-oauth-ui/src/main/resources/templates/header.html b/spring-security-oauth/spring-security-oauth-ui/src/main/resources/templates/header.html new file mode 100644 index 0000000000..db0cb5b6d3 --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-ui/src/main/resources/templates/header.html @@ -0,0 +1,56 @@ +
+ + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/spring-security-oauth/spring-security-oauth-ui/src/main/resources/templates/index.html b/spring-security-oauth/spring-security-oauth-ui/src/main/resources/templates/index.html new file mode 100755 index 0000000000..e2458c2940 --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-ui/src/main/resources/templates/index.html @@ -0,0 +1,30 @@ + + + + +Spring Security OAuth + + + + +
+ +
+

Foo Details

+
+ + {{foo.id}} +
+ +
+ +{{foo.name}} +
+ +
+New Foo +
+ +
+ + \ No newline at end of file diff --git a/spring-security-oauth/spring-security-oauth-ui/src/main/resources/templates/oauthTemp.html b/spring-security-oauth/spring-security-oauth-ui/src/main/resources/templates/oauthTemp.html new file mode 100644 index 0000000000..1efc1eed3c --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-ui/src/main/resources/templates/oauthTemp.html @@ -0,0 +1,6 @@ +
+ + Login + Access denied. Try again. + +
\ No newline at end of file diff --git a/spring-security-oauth/spring-security-oauth-ui/src/main/webapp/resources/oauth-ng.js b/spring-security-oauth/spring-security-oauth-ui/src/main/webapp/resources/oauth-ng.js new file mode 100644 index 0000000000..333070b935 --- /dev/null +++ b/spring-security-oauth/spring-security-oauth-ui/src/main/webapp/resources/oauth-ng.js @@ -0,0 +1,539 @@ +/* oauth-ng - v0.4.2 - 2015-08-27 */ + +'use strict'; + +// App libraries +angular.module('oauth', [ + 'oauth.directive', // login directive + 'oauth.accessToken', // access token service + 'oauth.endpoint', // oauth endpoint service + 'oauth.profile', // profile model + 'oauth.storage', // storage + 'oauth.interceptor', // bearer token interceptor + 'oauth.configuration' // token appender +]) + .config(['$locationProvider','$httpProvider', + function($locationProvider, $httpProvider) { + $httpProvider.interceptors.push('ExpiredInterceptor'); + }]); + +'use strict'; + +var accessTokenService = angular.module('oauth.accessToken', []); + +accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', '$interval', function(Storage, $rootScope, $location, $interval){ + + var service = { + token: null + }, + oAuth2HashTokens = [ //per http://tools.ietf.org/html/rfc6749#section-4.2.2 + 'access_token', 'token_type', 'expires_in', 'scope', 'state', + 'error','error_description' + ]; + + /** + * Returns the access token. + */ + service.get = function(){ + return this.token; + }; + + /** + * Sets and returns the access token. It tries (in order) the following strategies: + * - takes the token from the fragment URI + * - takes the token from the sessionStorage + */ + service.set = function(){ + this.setTokenFromString($location.hash()); + + //If hash is present in URL always use it, cuz its coming from oAuth2 provider redirect + if(null === service.token){ + setTokenFromSession(); + } + + return this.token; + }; + + /** + * Delete the access token and remove the session. + * @returns {null} + */ + service.destroy = function(){ + Storage.delete('token'); + this.token = null; + return this.token; + }; + + /** + * Tells if the access token is expired. + */ + service.expired = function(){ + return (this.token && this.token.expires_at && new Date(this.token.expires_at) < new Date()); + }; + + /** + * Get the access token from a string and save it + * @param hash + */ + service.setTokenFromString = function(hash){ + var params = getTokenFromString(hash); + + if(params){ + removeFragment(); + setToken(params); + setExpiresAt(); + // We have to save it again to make sure expires_at is set + // and the expiry event is set up properly + setToken(this.token); + $rootScope.$broadcast('oauth:login', service.token); + } + }; + + /* * * * * * * * * * + * PRIVATE METHODS * + * * * * * * * * * */ + + /** + * Set the access token from the sessionStorage. + */ + var setTokenFromSession = function(){ + var params = Storage.get('token'); + if (params) { + setToken(params); + } + }; + + /** + * Set the access token. + * + * @param params + * @returns {*|{}} + */ + var setToken = function(params){ + service.token = service.token || {}; // init the token + angular.extend(service.token, params); // set the access token params + setTokenInSession(); // save the token into the session + setExpiresAtEvent(); // event to fire when the token expires + + return service.token; + }; + + /** + * Parse the fragment URI and return an object + * @param hash + * @returns {{}} + */ + var getTokenFromString = function(hash){ + var params = {}, + regex = /([^&=]+)=([^&]*)/g, + m; + + while ((m = regex.exec(hash)) !== null) { + params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); + } + + if(params.access_token || params.error){ + return params; + } + }; + + /** + * Save the access token into the session + */ + var setTokenInSession = function(){ + Storage.set('token', service.token); + }; + + /** + * Set the access token expiration date (useful for refresh logics) + */ + var setExpiresAt = function(){ + if (!service.token) { + return; + } + if(typeof(service.token.expires_in) !== 'undefined' && service.token.expires_in !== null) { + var expires_at = new Date(); + expires_at.setSeconds(expires_at.getSeconds() + parseInt(service.token.expires_in)-60); // 60 seconds less to secure browser and response latency + service.token.expires_at = expires_at; + } + else { + service.token.expires_at = null; + } + }; + + + /** + * Set the timeout at which the expired event is fired + */ + var setExpiresAtEvent = function(){ + // Don't bother if there's no expires token + if (typeof(service.token.expires_at) === 'undefined' || service.token.expires_at === null) { + return; + } + var time = (new Date(service.token.expires_at))-(new Date()); + if(time && time > 0){ + $interval(function(){ + $rootScope.$broadcast('oauth:expired', service.token); + }, time, 1); + } + }; + + /** + * Remove the oAuth2 pieces from the hash fragment + */ + var removeFragment = function(){ + var curHash = $location.hash(); + angular.forEach(oAuth2HashTokens,function(hashKey){ + var re = new RegExp('&'+hashKey+'(=[^&]*)?|^'+hashKey+'(=[^&]*)?&?'); + curHash = curHash.replace(re,''); + }); + + $location.hash(curHash); + }; + + return service; + +}]); + +'use strict'; + +var endpointClient = angular.module('oauth.endpoint', []); + +endpointClient.factory('Endpoint', function() { + + var service = {}; + + /* + * Defines the authorization URL + */ + + service.set = function(configuration) { + this.config = configuration; + return this.get(); + }; + + /* + * Returns the authorization URL + */ + + service.get = function( overrides ) { + var params = angular.extend( {}, service.config, overrides); + var oAuthScope = (params.scope) ? encodeURIComponent(params.scope) : '', + state = (params.state) ? encodeURIComponent(params.state) : '', + authPathHasQuery = (params.authorizePath.indexOf('?') === -1) ? false : true, + appendChar = (authPathHasQuery) ? '&' : '?', //if authorizePath has ? already append OAuth2 params + responseType = (params.responseType) ? encodeURIComponent(params.responseType) : ''; + + var url = params.site + + params.authorizePath + + appendChar + 'response_type=' + responseType + '&' + + 'client_id=' + encodeURIComponent(params.clientId) + '&' + + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + + 'scope=' + oAuthScope + '&' + + 'state=' + state; + + if( params.nonce ) { + url = url + '&nonce=' + params.nonce; + } + return url; + }; + + /* + * Redirects the app to the authorization URL + */ + + service.redirect = function( overrides ) { + var targetLocation = this.get( overrides ); + window.location.replace(targetLocation); + }; + + return service; +}); + +'use strict'; + +var profileClient = angular.module('oauth.profile', []); + +profileClient.factory('Profile', ['$http', 'AccessToken', '$rootScope', function($http, AccessToken, $rootScope) { + var service = {}; + var profile; + + service.find = function(uri) { + var promise = $http.get(uri, { headers: headers() }); + promise.success(function(response) { + profile = response; + $rootScope.$broadcast('oauth:profile', profile); + }); + return promise; + }; + + service.get = function() { + return profile; + }; + + service.set = function(resource) { + profile = resource; + return profile; + }; + + var headers = function() { + return { Authorization: 'Bearer ' + AccessToken.get().access_token }; + }; + + return service; +}]); + +'use strict'; + +var storageService = angular.module('oauth.storage', ['ngStorage']); + +storageService.factory('Storage', ['$rootScope', '$sessionStorage', '$localStorage', function($rootScope, $sessionStorage, $localStorage){ + + var service = { + storage: $sessionStorage // By default + }; + + /** + * Deletes the item from storage, + * Returns the item's previous value + */ + service.delete = function (name) { + var stored = this.get(name); + delete this.storage[name]; + return stored; + }; + + /** + * Returns the item from storage + */ + service.get = function (name) { + return this.storage[name]; + }; + + /** + * Sets the item in storage to the value specified + * Returns the item's value + */ + service.set = function (name, value) { + this.storage[name] = value; + return this.get(name); + }; + + /** + * Change the storage service being used + */ + service.use = function (storage) { + if (storage === 'sessionStorage') { + this.storage = $sessionStorage; + } else if (storage === 'localStorage') { + this.storage = $localStorage; + } + }; + + return service; +}]); +'use strict'; + +var oauthConfigurationService = angular.module('oauth.configuration', []); + +oauthConfigurationService.provider('OAuthConfiguration', function() { + var _config = {}; + + this.init = function(config, httpProvider) { + _config.protectedResources = config.protectedResources || []; + httpProvider.interceptors.push('AuthInterceptor'); + }; + + this.$get = function() { + return { + getConfig: function() { + return _config; + } + }; + }; +}) +.factory('AuthInterceptor', function($q, $rootScope, OAuthConfiguration, AccessToken) { + return { + 'request': function(config) { + OAuthConfiguration.getConfig().protectedResources.forEach(function(resource) { + // If the url is one of the protected resources, we want to see if there's a token and then + // add the token if it exists. + if (config.url.indexOf(resource) > -1) { + var token = AccessToken.get(); + if (token) { + config.headers.Authorization = 'Bearer ' + token.access_token; + } + } + }); + + return config; + } + }; +}); +'use strict'; + +var interceptorService = angular.module('oauth.interceptor', []); + +interceptorService.factory('ExpiredInterceptor', ['Storage', '$rootScope', function (Storage, $rootScope) { + + var service = {}; + + service.request = function(config) { + var token = Storage.get('token'); + + if (token && expired(token)) { + $rootScope.$broadcast('oauth:expired', token); + } + + return config; + }; + + var expired = function(token) { + return (token && token.expires_at && new Date(token.expires_at) < new Date()); + }; + + return service; +}]); + +'use strict'; + +var directives = angular.module('oauth.directive', []); + +directives.directive('oauth', [ + 'AccessToken', + 'Endpoint', + 'Profile', + 'Storage', + '$location', + '$rootScope', + '$compile', + '$http', + '$templateCache', + function(AccessToken, Endpoint, Profile, Storage, $location, $rootScope, $compile, $http, $templateCache) { + + var definition = { + restrict: 'AE', + replace: true, + scope: { + site: '@', // (required) set the oauth server host (e.g. http://oauth.example.com) + clientId: '@', // (required) client id + redirectUri: '@', // (required) client redirect uri + responseType: '@', // (optional) response type, defaults to token (use 'token' for implicit flow and 'code' for authorization code flow + scope: '@', // (optional) scope + profileUri: '@', // (optional) user profile uri (e.g http://example.com/me) + template: '@', // (optional) template to render (e.g bower_components/oauth-ng/dist/views/templates/default.html) + text: '@', // (optional) login text + authorizePath: '@', // (optional) authorization url + state: '@', // (optional) An arbitrary unique string created by your app to guard against Cross-site Request Forgery + storage: '@' // (optional) Store token in 'sessionStorage' or 'localStorage', defaults to 'sessionStorage' + } + }; + + definition.link = function postLink(scope, element) { + scope.show = 'none'; + + scope.$watch('clientId', function() { + init(); + }); + + var init = function() { + initAttributes(); // sets defaults + Storage.use(scope.storage);// set storage + compile(); // compiles the desired layout + Endpoint.set(scope); // sets the oauth authorization url + AccessToken.set(scope); // sets the access token object (if existing, from fragment or session) + initProfile(scope); // gets the profile resource (if existing the access token) + initView(); // sets the view (logged in or out) + }; + + var initAttributes = function() { + scope.authorizePath = scope.authorizePath || '/oauth/authorize'; + scope.tokenPath = scope.tokenPath || '/oauth/token'; + scope.template = scope.template || 'bower_components/oauth-ng/dist/views/templates/default.html'; + scope.responseType = scope.responseType || 'token'; + scope.text = scope.text || 'Sign In'; + scope.state = scope.state || undefined; + scope.scope = scope.scope || undefined; + scope.storage = scope.storage || 'sessionStorage'; + }; + + var compile = function() { + $http.get(scope.template, { cache: $templateCache }).success(function(html) { + element.html(html); + $compile(element.contents())(scope); + }); + }; + + var initProfile = function(scope) { + var token = AccessToken.get(); + + if (token && token.access_token && scope.profileUri) { + Profile.find(scope.profileUri).success(function(response) { + scope.profile = response; + }); + } + }; + + var initView = function() { + var token = AccessToken.get(); + + if (!token) { + return loggedOut(); // without access token it's logged out + } + if (token.access_token) { + return authorized(); // if there is the access token we are done + } + if (token.error) { + return denied(); // if the request has been denied we fire the denied event + } + }; + + scope.login = function() { + Endpoint.redirect(); + }; + + scope.logout = function() { + AccessToken.destroy(scope); + $rootScope.$broadcast('oauth:logout'); + loggedOut(); + }; + + scope.$on('oauth:expired', function() { + AccessToken.destroy(scope); + scope.show = 'logged-out'; + }); + + // user is authorized + var authorized = function() { + $rootScope.$broadcast('oauth:authorized', AccessToken.get()); + scope.show = 'logged-in'; + }; + + // set the oauth directive to the logged-out status + var loggedOut = function() { + $rootScope.$broadcast('oauth:loggedOut'); + scope.show = 'logged-out'; + }; + + // set the oauth directive to the denied status + var denied = function() { + scope.show = 'denied'; + $rootScope.$broadcast('oauth:denied'); + }; + + // Updates the template at runtime + scope.$on('oauth:template:update', function(event, template) { + scope.template = template; + compile(scope); + }); + + // Hack to update the directive content on logout + // TODO think to a cleaner solution + scope.$on('$routeChangeSuccess', function () { + init(); + }); + }; + + return definition; + } +]);