Quantcast
Channel: WSO2IS – SOA Security
Viewing all 15 articles
Browse latest View live

Custom grant type with OAuth 2.0

$
0
0

OAuth 2.0 Authorization servers support for four main grant types according to the specification. Also it has given the flexibility to support any custom grant types. Today, I am going to implement a custom grant type for OAuth 2.0 Authorization server. Also we are going to see how we can extend the behavior of default grant types.

I am using WSO2 Identity Server as OAuth 2.0 Authorization Server implementation, which is an open source implementation.

Implementing new grant type

Step 1. If you are using Identity Server, you need to implement two extensions for this.

  • GrantTypeHandler  –  This is the implementation of the grant type.  Here you can implement the way, it must be validated and how token must be issued. You can write the new implementation by implementing “AuthorizationGrantHandler” interface or by extending the “AbstractAuthorizationGrantHandler”. Most of the cases, It is enough to extend the “AbstractAuthorizationGrantHandler” in WSO2 OAuth component.
  • GrantTypeValidator – This is used to validate the grant request that is sent to the /token end point. You can define what parameters must be in the request and define the validation of them. You can write the new implementation by extending the “AbstractValidator” in Apache Amber component.

Step 2. When implementation is done, you need to package your class as jar file and put in to the <IS_HOME>/repository/component/lib directory.

Step 3. Then you need to register the grant type with unique identifier. You can do it by adding new entry for identity.xml file. Here, you need to define your implementation class.


<SupportedGrantType>
 <GrantTypeName>grant type identifier </GrantTypeName>
 <GrantTypeHandlerImplClass>full qualified class name of grant handler</GrantTypeHandlerImplClass>
 <GrantTypeValidatorImplClass>full qualified class name of grant validator</GrantTypeValidatorImplClass>
 </SupportedGrantType>
 <SupportedGrantType>

Sample Grant Type implementation

I am going to define new sample grant type called, “mobile” grant type. It is same as password grant type, only different that you need to pass the mobile number.

Request to /token API must contain following two request parameters

  •  grant_type=mobile
  • mobileNumber=044322433

Please find my new grant type project from here, You can find the grant handler and validator class inside “org.soasecurity.is.oauth.grant.mobile” package. You can modify them as you want.

Try Out

Step 1. Copy jar file in to <IS_HOME>/repository/component/lib (You can even modify the project and build using maven 3)

Step 2. Configure following in the identity.xml file inside the <OAuth><SupportedGrantTypes> element

<SupportedGrantType>
<GrantTypeName>mobile</GrantTypeName>
<GrantTypeHandlerImplClass>org.soasecurity.is.oauth.grant.mobile.MobileGrant</GrantTypeHandlerImplClass>
<GrantTypeValidatorImplClass>org.soasecurity.is.oauth.grant.mobile.MobileGrantValidator</GrantTypeValidatorImplClass>
</SupportedGrantType>

Step 3. Restart the server.

Step 4. You can register, OAuth application as mentioned in there.

Step 5. Then send the grant request to /token API using curl.

HTTP POST body must contains  following two parameters.  i.e  grant_type=mobile  and mobileNumber

grant_type=mobile&mobileNumber=0333444
curl --user j35X8UIc5KXMJXgcWIChVMffv6ca:6FOPU8JrQDZqMu4GugfHpbtD_vsa -k -d "grant_type=mobile&mobileNumber=0333444" -H "Content-Type: application/x-www-form-urlencoded" https://localhost:9443/oauth2/token

you will receive following json response with access token

{"token_type":"bearer","expires_in":2823,"refresh_token":"26e1ebf16cfa4e67c3bf39d72d5c276","access_token":"d9ef87802a22cf7682c2e77df72c735"}

Customizing existing grant type

You can even customize existing grant types. I have implemented following two class to customize the password grant type. However, you can do it for any grant type.

As an example, if you want to try out the second implementation.

Step 1. Copy jar file in to <IS_HOME>/repository/component/lib (You can even modify the project and build using maven 3)

Step 2. Modify password grant type class in the identity.xml. It would be as follows.

<SupportedGrantType>
<GrantTypeName>password</GrantTypeName>
<GrantTypeHandlerImplClass>org.soasecurity.is.oauth.grant.password.ModifiedAccessTokenPasswordGrant</GrantTypeHandlerImplClass>
</SupportedGrantType>

Step 3. Restart the server.

Step 4. You can register, OAuth application as mentioned in there.

Step 5. Then send password grant request to /token API using curl.

curl --user j35X8UIc5KXMJXgcWIChVMffv6ca:6FOPU8JrQDZqMu4GugfHpbtD_vsa -k -d "grant_type=password&username=admin&password=admin" -H "Content-Type: application/x-www-form-urlencoded" https://localhost:9443/oauth2/token

You can see the modified access token with some email address.

{"token_type":"bearer","expires_in":2955,"refresh_token":"6865c8d67b42c0c23e634a8fc5aa81f","access_token":"982f40d4-0bb6-41ce-ac5a-1da06a83e475asela@soasecurity.org"}

Thanks for reading …!!!


JAX-WS client for WSO2 Admin service.

$
0
0

In my previous blog post, we understood about Admin services in WSO2 products. We have implemented a web service client for RemoteUserStoreManagerService. We used Axis2 client for that. Today, we are going to look, how we can implement a JAX-WS cleint for invoking admin services.

I am using the RemoteUserStoreManagerService as sample admin service.

Step 1. Generating stub class using wsimport

>wsimport

Due to issue of java2wsdl tool which is used in the WSO2 severs, generated WSDL file does not contain output message element for void return types with faults. Therefore wsimport would be failed with errors. However, you can modify the WSDL and use wsimport command to generated the stubs.  Basically we need to consider the these operations as two way operations as they are returning an application level exceptions.

How to modify the WSDL ?

1. Download the original WSDL by pointing to correct url of the server

2. Update it by adding <output> element if element is not presented under the <operation>.  You must do to it for all the operations elements which contains a <fault>

As an example,  “addUser” operation of the default WSDL would be as follows


<wsdl:operation name="addUser">
<wsdl:input message="ns:addUserRequest" wsaw:Action="urn:addUser"/>
<wsdl:fault message="ns:RemoteUserStoreManagerServiceUserStoreException" name="RemoteUserStoreManagerServiceUserStoreException" wsaw:Action="urn:addUserRemoteUserStoreManagerServiceUserStoreException"/>
</wsdl:operation>

You can modify it by adding output element… Just add following


<wsdl:output message="ns:addUserRequest" wsaw:Action="urn:addUserResponse"/>

Then complete element would be as follows.


<wsdl:operation name="addUser">
<wsdl:input message="ns:addUserRequest" wsaw:Action="urn:addUser"/>
<wsdl:output message="ns:addUserRequest" wsaw:Action="urn:addUserResponse"/>
<wsdl:fault message="ns:RemoteUserStoreManagerServiceUserStoreException" name="RemoteUserStoreManagerServiceUserStoreException" wsaw:Action="urn:addUserRemoteUserStoreManagerServiceUserStoreException"/>
</wsdl:operation>

Also, you need to configure following element as well.


<wsdl:operation name="addUser">
<soap12:operation soapAction="urn:addUser" style="document"></soap12:operation>
<wsdl:input>
<soap12:body use="literal"></soap12:body>
</wsdl:input>
<wsdl:fault name="RemoteUserStoreManagerServiceUserStoreException">
<soap12:fault use="literal" name="RemoteUserStoreManagerServiceUserStoreException"></soap12:fault>
</wsdl:fault>
</wsdl:operation>

You can modify it by adding output element… Just add following


<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>

Then complete element would be as follows.


<wsdl:operation name="addUser">
<soap:operation soapAction="urn:addUser" style="document"></soap:operation>
<wsdl:input>
<soap:body use="literal"></soap:body>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
<wsdl:fault name="RemoteUserStoreManagerServiceUserStoreException">
<soap:fault use="literal" name="RemoteUserStoreManagerServiceUserStoreException"></soap:fault>
</wsdl:fault>
</wsdl:operation>

You no need to much worry about the content of the <output> element, as we are just adding it for code generation purpose.

You can find the updated WSDL from here

Step 2. Create a jar file using generated stub class. I just used the jar command for it. You can find the created jar file from here.

 >jar cvf remote-user-mgt-stub.jar *

Step 3. Create your java project by adding above stub jar file in to the class path

Sample java client side code for listuser() operation would be as following.


//set the trust store which contains the WSO2IS server's certificate
System.setProperty("javax.net.ssl.trustStore", "/home/asela/is/wso2is-5.0.0/repository/resources/security/client-truststore.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "wso2carbon");

RemoteUserStoreManagerService managerService = new RemoteUserStoreManagerService();

RemoteUserStoreManagerServicePortType portType = managerService.getRemoteUserStoreManagerServiceHttpSoap11Endpoint();

BindingProvider bindingProvider = (BindingProvider) portType;
bindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "https://localhost:9443/services/RemoteUserStoreManagerService");

bindingProvider.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "admin");
bindingProvider.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "admin");

try {
List<String> users = portType.listUsers("*", 100);
for(String user: users){
System.out.println("User : " + user);
}
} catch (RemoteUserStoreManagerServiceUserStoreException_Exception e) {
e.printStackTrace();
}

Step 4 (Optional). If you check the code generation class, you will see JAXBElement elements which make difficult to implement the client side code . Therefore, when you are code generating, you can ignore the JAXBElement element as follows.

1. Please create binding file as follows


<jaxb:bindings
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" jaxb:version="2.0"
xmlns:xjc= "http://java.sun.com/xml/ns/jaxb/xjc" jaxb:extensionBindingPrefixes="xjc">
<jaxb:globalBindings generateElementProperty="false">
<xjc:simple />
</jaxb:globalBindings>
</jaxb:bindings>

2. Run wsimport by pointing to the binding file.

>wsimport -b simple-binding.xml

Thanks for reading.!!!

SSO without Identity Provider login page ?

$
0
0

I have seen some of the people you are using SSO mechanism (SAML2 SSO, OpenId , OpenID Connect) have raised this in several places. Answer is “Yes“.. it can be done. Simple way is that, Service provider can promote a login page for the end users (or else Service provider can retrieve end user’s credentials some other way) and then credentials can be sent to the IDP with the login request.

But when it comes to the end user experience, I think, we are trying to cheat the end user. We just promote the login page in Service Provider and ask to provide the credentials. Then user thinks that credentials are given to SP but it is not, they have been given to the IDP.

However, Lets how this can be achieve with WSO2IS which is open source Identity management Server.

Request Path Authenticators in WSO2IS

 

In WSO2IS, Request Path Authenticators are a type of local authenticators that are meant to authenticate requests that contain the user’s credentials. By default, this will be handled in following two ways.

  • User Credentials in “sectoken” parameter
  • User Credentials in Authorization Header

Service provider can send the end user credentials in above two way in the login request. User credentials separated by a colon (:) and encoded with Base64 using one of the above methods.

Please note, When sending the user credentials from the client application to the WSO2 Identity Server,it is recommended to use;

  • SSL
  • POST the parameters instead of GET. This is because when the parameters are sent in GET requests, the URL will contain the encoded credentials which can be printed in the access logs files of the Identity Server or some other proxy servers.

Try-out

 

Let try the request path authenticator with SAML2 SSO flow. You can find the more details on configuring Identity Server as SAML2 SSO IDP from here, I assume that we have already go through it. Then,

Step 1. In the Local & Outbound Authentication configuration, add the Request Path Authentication configuration as basic-auth.

rpa1
Step 2. We need to modify the sample SSO application to send the end user’s credentials. In the sample application, once user credentials are entered,  they must be sent to Identity Server in either of the following ways.

1. In the Authorization header

Authorization: Basic <base64_encode(username:password)>

Important  Note :  This may not work properly with SAML2 SSO as it is a browser redirection. HTTP header can not be sent 

2. In the sectoken parameter
sectoken=<base64_encode(username:password)>

As mentioned above, SP need send the SAML2 Auth request using SAML2 SSO POST binding , NOT redirect binding. You can find the modified  war file for the updated sample application from here.  This sample uses the sectoken parameter

rpa2
Once you enter the credentials in to the sample application , you will not see the IDP login page..

Thanks for reading…!!!

Configure Attribute Stores with WSO2 Identity Server.

$
0
0

Consider about a scenarios that enterprise user’s details can be found in two place. User credentials may be kept in one user store and User’s attribute may be stored in another user store. Lets see how WSO2 Identity Server can be used to merge these two user stores and retrieve the user’s details in unique manager.

WSO2 Identity Server has great extension capabilities, therefore any type use cases can be easily supported by writing simple extension point. Please refer my previous blog post to learn more about the user management extensions.

First, lets take an example,

attribute0

 

There are four LDAP based user stores, LDAP-1 and LDAP-3 are mainly considered as credentials stores which user’s credentials have been stored. LDAP-2 and LDAP-4 are attribute stores of the users which user details are stored. LDAP-1’s corresponding attribute store is LDAP-2. It means that all the users in LDAP-1 can be found in LDAP-2 as well. But LDAP-2 may not contain the credentials of the user and it contains the user’s attributes or some additional attributes other than in the LDAP-1. Same way, LDAP-3’s corresponding attribute store is LDAP-4.
Step 1. Configure all four user store with WSO2IS. Lets configure LDAP-1 as primary user store of the WSO2IS. Primary user store can be configured using the user-mgt.xml file. Then lets configure the the other user stores as secondary user stores from the UI.

When you are configuring the Attribute User store, you need to follow special notation for domain name.

Two simple rules

1. Domain must be same as the corresponding credentials store

2. Domain name must be qualified with a post prefix called   “-ATTRIBUTE-STORE”

As an example,

We have configured LDAP-1 as primary user store. Therefore its domain name is PRIMARY. So, LDAP-2 must be configured as domain name with PRIMARY-ATTRIBUTE-STORE

Say, we have configured the LDAP-3 with domain name XACMLINFO.COM Then corresponding attribute store which is LDAP-4 must be configured with domain name XACMLINFO.COM-ATTRIBUTE-STORE

Now, you have properly configured multiple user stores.
Step 2. Download and Copy the custom extension which can be found at here in to the <WSO2IS_HOME>/repository/components/dropins directory .

You can find Maven project for the custom extension from here. It is simple java project that has been implemented using extending a listener interface. You can modify and do anything as you like.

Step 3. Restart the server.

Step 4. Try out.

You can not authenticate by providing the attribute user store domain
When you retrieve attribute for given user in credential store, you would receive the attributes from corresponding attribute stores.

You can configure WSO2IS as SAML2 SSO IDP and try out this easily, Please refer more details for here.

Thanks for reading…!!!

OpenId Connect support with resource owner password grant type

$
0
0

According to the OpenId Connect specification, It is recommended to use authorization code and implicit grant types for OpenId Connect requests. But it is not mentioned that other grant types can not be used. Therefore you can use any other grant types for OpenId Connect authentication request. Some OAuth2 Authorization server supports for password grant type to obtain the id_token. Also there can be custom grant types as mentioned here.

WSO2IS also supports for granting an id_token with password grant type, Let see how it works.
Step 1. Register an OAuth application using WSO2IS management console.

Important : If you are using WSO2 APIM, You do not need this step. Once you subscribe to application, API Store would register an OAuth subscription automatically.

You can go to service provider configuration page and register a SP application. Then can configure the OAuth in bound authentication details

id1

id2

 

id3

 

Callback url can be any url  as we are only using password grant type,  it is not important.
OAuth consumer key and secret are generated for you

id4

Step 2. Send openid connect request using password grant type. Your request would be as follows,

POST /token HTTP/1.1
 Host: server.example.com
 Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
 Content-Type: application/x-www-form-urlencoded

grant_type=password&username=asela&password=asela&scope=openid

Authorization header is created with base64 encoded client id and secret values

You can use simple curl command to generate the request. Sample curl command would be as follows.

curl --user  ZTh12LlAv8gU0I32KgCbwM5ouJ4a:XQ789Q0mfPr7VSNpp_MHhz4Pkeka 
-k -d "grant_type=password&username=asela&password=asela&scope=openid" 
-H  "Content-Type: application/x-www-form-urlencoded" https://localhost:9443/oauth2/token

you will receive the id_token in the response.

{"scope":"openid","token_type":"bearer","expires_in":1807,"refresh_token":"bb42f2626a3aede2f13dbc95b9b448c5",
"id_token":"eyJhbGciOiJSUzI1NiJ9.eyJhdXRoX3RpbWUiOjE0MjU4OTM5NTMyMTUsImV4cCI6MTQyNTg5OTA0NTQyMywic3ViIjoiYXNlbGFAY2FyYm9uLnN1cGVyIiwiYXpwIjoiWlRoMTJMbEF2OGdVMEkzMktnQ2J3TTVvdUo0YSIsImF0X2hhc2giOiJNVFZrT1Rkak9UWmhabUV5TlRoa1kyTTFNVGMyTkdRMU9UVmxabU00WmpRPSIsImF1ZCI6WyJaVGgxMkxsQXY4Z1UwSTMyS2dDYndNNW91SjRhIl0sImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTQ0M1wvb2F1dGgyZW5kcG9pbnRzXC90b2tlbiIsImlhdCI6MTQyNTg5NTQ0NTQyM30.EGecxRcOrtR4NAuWel-6Rx6jrI759Y9Jztv9TrPfLcLlBFBQWIJRVfGLMUbUURs3Y0vmIH-2KCbWhFdSz_6f3Pchk3pKwrGGFiTpyVEmjE217fFT3c3Dz__tHlpJJE30DUeHKObfkZpPdv5SoRTlp3IKaM2PBwWqoLeylgaUPBs",
"access_token":"15d97c96afa258dcc51764d595efc8f4"}

You can Base64 decode the id_token and see the content of it.   Please note id_token is a JWT token and it contains the header, body and signature which are separated with the  dot (.) .   Therefore you need to properly separate each component and decode it.

Decoded sample token body would be as following

{"auth_time":1425893953215,"exp":1425899045423,"sub":"asela@carbon.super",
"azp":"ZTh12LlAv8gU0I32KgCbwM5ouJ4a","at_hash":"MTVkOTdjOTZhZmEyNThkY2M1MTc2NGQ1OTVlZmM4ZjQ=",
"aud":["ZTh12LlAv8gU0I32KgCbwM5ouJ4a"],"iss":"https:\/\/localhost:9443\/oauth2endpoints\/token",
"iat":1425895445423}

Step 3.   Retrieving user attributes.  You can retrieve the user attributes by calling /userinfo endpoint as well.  Your request would be as follows.

 GET /userinfo HTTP/1.1
 Host: server.example.com
 Authorization: Bearer 15d97c96afa258dcc51764d595efc8f4

Sample curl command for it

curl -k -H "Authorization: Bearer 15d97c96afa258dcc51764d595efc8f4"   https://localhost:9443/oauth2/userinfo?schema=openid

User’s attributes would be returned as json response…

{"phone_number":"+94777625933","email":"asela@soasecurity.org",
"name":"asela","family_name":"Pathberiya","preferred_username":"asela",
"given_name":"asela","country":"United States"}

Thanks for reading…

Customizing SAML2 Response and SAML2 Assertion in WSO2

$
0
0

WSO2IS provides extensions to customize the SAML2 response and Assertion that is generated with SAML2 SSO web browser profile. You can find more on SAML2 SSO with WSO2IS from here as well.

Lets go through quick to on customizing SAML2 Response and Assertion.

Step 1. First, you need to implement a new SAML2 Response or Assertion builder class by implementing the SAMLAssertionBuilder and ResponseBuilder interfaces of the SAML2 SSO OSGI component (which is known  as org.wso2.carbon.identity.sso.saml)

Also, you can write your own implementation by extending the default implementations which are available in the org.wso2.carbon.identity.sso.saml component.

Here is simple sample implementation for both customized SAML2 Response and Assertion builder… In this customization, we are just only customizing the default signature algorithm that is used by WSO2IS to generate the SAML2 signature. By default WSO2IS uses RSA-SHA1 which is according to the specification.  But we are going to make it as RSA-SHA256

Step 2. Create a jar file using your customized implementation and copy it in to <WSO2IS_HOME>/repository/component/lib directory. You can just copy the sample jar file in here  which is used in above sample project.

Step 3. Register your custom class names using identity.xml file which can be found at <WSO2IS_HOME>/repository/conf directory.

You need configure the full qualified class name of your custom implementations under the <SSOService> element

For custom response builder  <SAMLSSOResponseBuilder>RSA-SHA256

<SAMLSSOResponseBuilder>org.soasecurity.is.saml2.custom.response.CustomSAML2ResponseBuilder</SAMLSSOResponseBuilder>

For custom assertion builder  <SAMLSSOAssertionBuilder>

<SAMLSSOAssertionBuilder>org.soasecurity.is.saml2.custom.response.CustomSAML2AssertionBuilder</SAMLSSOAssertionBuilder>

Step 4. Restart the server and see, It would pick your custom implementation.
Step 5. You can further debug your custom code by starting the WSO2IS server in debug mode as following.

>sh wso2server.sh -debug 5005

Thanks for reading.

[Federated Authentication] Integration OpenAM with WSO2IS using Openid-Connect

$
0
0

In my previous blog post, we went through how you can configure the SAML2 SSO web application with Identity Server. Users authenticate to Identity Server by proving username/password. These username/password must be authenticated with the enterprise user store that is deployed with Identity Server. Therefore; only the user who are in the enterprise user store can access the web application.

Assume, you have a new requirement that web application must be accessed by the users from some other partner organization. Partner organization has their employee’s user accounts in LDAP Server. Partner organization can not expose this LDAP server in to Enterprise Identity Server as a user store due to security reasons. But partner organization has OpenAM Identity Provider which has been connected with LDAP server. OpenAM supports SAML2 SSO/Openid-Connect and it authenticates user with LDAP server. Therefore users from partner organization who need to login to the web application can be redirected to OpenAM IDP and can be authenticated with their own LDAP Server. How we are going to achieve this?

With Identity Server, you can configure multiple Federated Identity Providers that users can be authenticated. In this use case, users from its own enterprise can be authenticated with enterprise user store and users from partner organization, can be authenticated with OpenAM IDP.

am20

 

Now let see how we can integrate OpenAM and WSO2IS using Openid-Connect.

Configure WSO2IS as OpenId-Connect Client Application in OpenAM

 

Step 1. Please find more details on Openid-Connect client application registration from here. You want to go through same steps. I would like to highlight some important steps using the screen shots.

 

You can provide the name and password which are the OAuth2 client id and client secret.

am4

Configure scope value to be “openid”

am11

Configure the call back url for your WSO2IS, this must be following value.

https://{HOST_NAME}:{PORT}/commonauth

am10

 

Configure OpenAM as Federated OpenId-Connect IDP

 

Step 1. Login to WSO2IS management console

Step 2. Register new Federated IDP

You need provide a name for IDP configuration. Say “OpenAM-IDP”

am21

 

Go to “OAuth2/OpenID Connect Configuration” and Register following details.

am9

1. Enable / Default – You can enable and set as default

2. Authorization Endpoint URL – http://localhost:8080/OpenAM-12.0.0/oauth2/authorize

3. Token Endpoint URL – http://localhost:8080/OpenAM-12.0.0/oauth2/access_token

4. Client Id – WSO2IS (Value that is provided by us when registering in OpenAM)

5. Client Secret – (Value that is provided by us when registering in OpenAM)

6. Additional Query Parameters – scope=openid (This is not required. By default, WSO2IS sends the openid scope)

 

Step 3. Configure OpenAM-IDP as Federated IDP for our Web application.

There are two ways that you can attach the OpenAM-IDP as Federated IDP.

One way is, just configure it as a federated authentication IDP for web application. Then, once user tries to access the web application, user would be redirect OpenAM-IDP login page via the identity Server. In here, only the users who can be authenticated via OpenAM-IDP, can login to web application.

am22

Or less, you can configure using Advance Configuration. Here we configure one step that contains two option of authentication. One is basic authentication that allows to authenticate users from enterprise user store. Other one is OpenAM-IDP. Once you configure like this, users who are accessing to web application would be promoted a IDP login page with both options. Therefore users from OpenAM-IDP and enterprise user store can login to web application.

am23

 

Try out

 

Lets try to login to web application. If you have configured using Advance Configuration, you would see following login page in the Identity Server.

am24
When you click on OpenAM-IDP, you would be redirected to OpenAM login and user consent pages.

am25

am26

By default, WSO2IS extracts the username from id_token  (“sub” attribute) and sends it back to the Web application using SAML2 SSO

 

am27
Thanks for reading…!!!

User Password Hashing with WSO2 Identity Server (WSO2IS)

$
0
0

If you are storing end user passwords, It must be stored as hashed value.. not as encrypted or plain text. Because; once it is stored as hashed, it is hard to find the actual password out of it. So, it is guarantee more security for your end users.

Let see how we can properly configure WSO2 Identity Server to hash the passwords.

WSO2 IS can be connected with two main types of user store implementations i.e JDBC based user stores and LDAP based user stores. Password hashing configurations and methods would be changed based on the connected user store.

Storing passwords in JDBC user stores

 

When you have enabled the JDBC user store of WSO2 IS (or any other Carbon based products), user passwords are stored as salted hashed by default.

There are two user store configuration properties that governs the password storing which are followings.


<Property name="PasswordDigest">SHA-256</Property>
 <Property name="StoreSaltedPassword">true</Property>

PasswordDigest property can be used to define the algorithm.. All the algorithms which are supported by JAVA can be used here. Please check the supported algorithms by JAVA 7 from here.

Please note, If you just configure as SHA , It is consider as SHA-1

It is always better to configure algorithm with higher bit value as digest bit size would be  increased.

SHA-1 password

salt1
SHA-512 password

salt2
Storing Salted Hash Passwords

 

As mentioned, by default WSO2IS stores the password with salted value. It is said that the best way to protect passwords is to employ the salted password hashing. Once it is salted, Dictionary and Brute Force Attacks against the passwords would be more difficult.

As an simple example, if we set following property to false

<Property name="StoreSaltedPassword">false</Property>

Passwords would be hashed without a slated value. If two users (bob and alice) have the same password, they would be stored as same hashed value.

salt3
But, if salted password is used, WSO2IS would add some random value to password and generate the hash of it. Therefore if two users have same passwords, they would be stored as different hashed values which is more secure and recommended way to do it.

Storing passwords in LDAP user stores

 

Most of the LDAP servers (such as OpenLdap, OpenDJ, AD, ApacheDS and etc..) are supported to store password as salted hashed values (SSHA)

Therefore WSO2IS server just wants to feed password in to the connected user store as plain text value. Then LDAP user store can stored them as salted hashed value. To feed the plain text in to the LDAP server, you need to configure following user store property with value “PLAIN_TEXT”

<Property name="PasswordHashMethod">PLAIN_TEXT</Property>

But; if your LDAP does not support to store user password as hashed values. You can configure WSO2IS to hash the password and feeds the hashed password in to the LDAP server. Then you need to configure PasswordHashMethod property with SHA (SHA-1), SHA-256, SHA-512.. Basically All the message digest algorithms which are supported by JAVA can be used here.

<Property name="PasswordHashMethod">SHA</Property>

Please note that, WSO2IS can not create a salted hashed password (SSHA) to feed in to the LDAP. Therefore, if you are using a LDAP server, the best way is to configure your LDAP server to store password as SSHA.

In theory; when you are using LDAP server with WSO2IS. Please configure your LDAP to store password as SSHA and then configure the “PasswordHashMethod” property to “PLAIN_TEXT”
Thanks for reading…!!!


Configure Multiple Federated Identity Providers with WSO2 Identity Server (WSO2IS).

$
0
0

In my previous posts, we tried the federation authentication with WSO2IS and other third party identity provider such as Salesforce, Google IDP, Shibboleth and so on. You can find them from here

In this post, we are trying to understand,, how to configure multiple federated IDPs with given service provider.
Let assume that we have already configure Google Salesforce and Shibboleth as federated IDP.

Step 1. Go to “Advance Configuration” of the Local and Outbound authentication configuration.

aidp2

Step 2. Select the Google, Salesforce and Shibboleth IDP in given step as followings.

aifp1

Step 3. Try to login to Service provider. Then end user would be redirected to WSO2IS

Step 4. SSO login page of WSO2IS would be shown as following. End user needs to select the desired IDP out of three IDPs listed in here.

aidp3

 

Say; Service provider already knows the IDP which end user is belonged or which end user must be selected. Then, there is no need to select the IDP by the end user using WSO2IS login page.

You need to go through following steps to achieve this.

Step 1. Define a “Home Realm Identifier:” value for each Federated IDP.
As an example, for Google-IDP, we can define the value as “googleIdp”

fidp
Step 2. Service provider must sent the “Home Realm Identifier” value as the query parameter in the authentication request.

As Service provider already knows the IDP, Then SP can send the “Home Realm Identifier” value for given IDP using “fidp” query parameter.

If it is google IDP, Then value must be

fidp=googleIdp

Step 3. WSO2IS finds the IDP related to the “fidp” value and redirects the end user to the IDP directly rather than showing the SSO login page.
Thanks for reading…!!!

 

Custom notification module for account management in WSO2 Identity Server (WSO2IS)

$
0
0

WSO2IS supports for account recovery/validation using identity management features. By default; it is supported to send the user notifications using emails. WSO2IS contains an email sending module with WSO2IS which is based on Axis2. But, you can configure any other notification modules with WSO2IS or extend the existing email sending module.

This blog post describes how you can add custom notification modules.
Step 1. Implement custom notification module by implementing the org.wso2.carbon.identity.mgt.NotificationSendingModule interface. You can find sample project from here. This module is sending a JSON payload to given endpoint as the notification.

Step 2. Copy your implementation as jar file in to the /repository/components/lib directory.

Step 3. Register the module using /repository/conf/security/identity-mgt.properties file.

Please add following property in to the file.

Identity.Mgt.Notification.Sending.Module.2=org.soasecurity.identity.mgt.notification.module.JSONNotificationModule

Step 4. Restart the server.

Step 5. Try out by sending the notification type as JSON. Please note Value “JSON” is configured in the custom module as the “getNotificationType()” value.

Step 6.notifyUser()” method of your implementation would be executed. In sample module, you can see that it is sent a JSON in to given end point url.

Thanks for reading…!!!

How to configure session time out in WSO2 Identity Server (WSO2IS)

$
0
0

I have seen many queries on configuring SSO session time out in WSO2IS. First, you need to understand that WSO2IS creates separate SSO session for SSO login and it is different from the session which is created when you are login to WSO2IS management console.

Let see how you can configure the SSO session time out with WSO2IS.

When end user login through WSO2IS for service provider application (Using SAML2 SSO, Openid, Openid Connect, Passive STS and etc ), WSO2IS creates a SSO session for end user and a cookie which is related to the created SSO session, is set in to the user’s browser.

This cookie can be seen as “commonauthId“. It is set in to the user’s browser with the hostname of WSO2IS instance and the value of the “commonauthId” cookie is the SSO session identifier.
When SSO session is created in WSO2IS, session is put in to the session cache and persist it in to the database. To persist it in to the database, you must enable the session persistence

 

Why it is important to persist the SSO session ?

 

SSO sessions have been stored in a in-memory cache. It is recommend to persist the SSO session due to following reasons.

1. If you are running single WSO2IS instance. If server is restarted, all SSO session would be removed. If you have multiple nodes of WSO2 instances, It is not guarantee that you can recover all the sessions. Although cache is distributed, it is not 100% split to each nodes.

2. Cache has a limit. If there are large number SSO sessions, memory can be high and server performance may reduce. So; usually cache is evicted after given number of entries (by default 10000 entries). Therefore, some SSO session can be evicted from caches when there are large number of user logins.

3. When there is a clustered development; If you have no persistence, you need to 100% rely on the distributed cache. But if you have persistence, you can rely on it as well which increases the reliability of the overall system.

 

How to enable session persistence ?

 

You can enable it using following property in identity.xml file.

<SessionDataPersist>
 <Enable>true</Enable>

After WSO2IS 5.1.0, it has been enabled by default.

 

How to disable and configure session cache ?

 

If you need, you have flexibility to configure session cache using identity.xml file.

 <SessionContextCache>
 <Enable>true</Enable>
 <Capacity>100000</Capacity> 
 </SessionContextCache>

You can enable/disable it and configure the cache capacity (capacity which eviction starts in caching)

 

How to configure SSO session time out ?

 

WSO2IS contains a idle session time out for SSO sessions. You can configure the idle time out value. By default, it is set to 15min. It means that if WSO2IS does not received any SSO authentication request for 15min for given user, SSO session would be timeout.

If you are using WSO2IS 5.1.0. you can configure it using following property in repository/conf/identity/identity.xml file

<TimeConfig>
 <SessionIdleTimeout>15</SessionIdleTimeout>

If you are using WSO2IS 5.0.0, you can configure it using following property in repository/conf/tomcat/carbon/WEB-INF/web.xml file

<session-config>
 <session-timeout>15</session-timeout>
 </session-config>

You can configure this for higher value in a real deployment. Default 15min is not enough for usual SSO login.

 

How remember me works ?

 

If you check the “commonauthid” cookie, cookie’s expiry time is set to “At end of session”. It means that if you close the browser/restart your machine, cookie would be removed. So; when you close you browser after SSO login, Your SSO session in WSO2IS would be invalidated.

Therefore; if you need to remember the SSO session, you need to tick on the Remember Me option in WSO2IS login page. Then “commonauthid” cookie is set with some defined expiry value. Then cookie will contain in the browser till it expired.

Expiry time of the “commonauthid” cookie, can be configured from following property.

If WSO2IS 5.1.0 using repository/conf/identity/identity.xml file

<TimeConfig>
<RememberMeTimeout>20160</RememberMeTimeout>

If WSO2IS 5.0.0 using repository/conf/identity.xml file

<SessionDataPersist>
 <Enable>true</Enable>
 <RememberMePeriod>20160</RememberMePeriod>

It is set to 14 days (2 weeks) by default.

 

What is session persistence time out ?

 

You can find the session cleanup time out configuration in the identity.xml file. This value is used to find out the timeout sessions which must be needed to remove from database.

If WSO2IS 5.1.0 repository/conf/identity/identity.xml file

<TimeConfig>
 <PersistanceCleanUpTimeout>20160</PersistanceCleanUpTimeout>

If WSO2IS 5.0.0 repository/conf/identity.xml file

<SessionDataPersist>
 <CleanUp>
 <TimeOut>20160</TimeOut>

It is important to configure higher value for this. Basically; this value must be larger than idle session time out and remember me time out value.

Thanks for reading..!!!

Custom authenticator for WSO2 Identity Server (WSO2IS) SSO login

$
0
0

In SSO login, you can plug different custom authenticators in to the WSO2IS. There are two major types. One is local authenticators and other one is Federated Authenticators. Federated authenticators are needed when you need to provide browser based redirections to another IDP. As an example Salesforce, Google, Facebook IDPs. (More details from here) In other cases, we can implement a local authenticator.

Default authenticator of WSO2IS, is the Basic authenticator and it is also a local authenticator which authenticates the end user with connected user store using provided username and password.

In this post, lets try to implement a local authenticator for WSO2IS by extending the basic authenticator.
Lets take following two sample requirements.

1. Users who are login using “OpenId Connect” must be authenticated with user store and must be authorized. In authorization, user must be verified that user is assigned to the specific role called “openidConnectRole”

2. Users who are from application (service provider) “soasecurity.org” must be authenticated with user store and must be authorized. In authorization, user must be verified that user contains a specific attribute. As an example, user contains a mail with domain “soasecurity.org”

 

Step 1. Extend the Basic Authenticator and implement a new authenticator.

As we are only worry about authorization, Authentication can be happened as in the Basic Authenticator level. We need to add authorization logic after the authentication. Therefore we only need to extend an one method in the Basic Authenticator which is “processAuthenticationResponse()”

Also, we need to modify the authenticator name by extending “getFriendlyName()” and “getName()” methods.

You can fine the extend authenticator source from here.

Step 2. Create OSGI bundle out of your extended authenticator. Sample project can be found from here.  You can build the project using maven3

Step 3. Deploy OSGI bundle in WSO2 Identity Server (or APIM) by copying in to /repository/components/dropins directory.

Step 4. You need to edit the /repository/deployment/server/webapps/authenticationendpoint/login.jsp page with your new authenticator name.

Because currently it is set to “BasicAuthenticator”. You can modify it as following by adding check for “BasicCustomAuthenticator”

if(localAuthenticatorNames.contains("BasicAuthenticator") | localAuthenticatorNames.contains("BasicCustomAuthenticator")){

 if(localAuthenticatorNames.size()>0 && (localAuthenticatorNames.contains("BasicAuthenticator") || } else if(localAuthenticatorNames.size()>0 && (localAuthenticatorNames.contains("BasicAuthenticator") || localAuthenticatorNames.contains("BasicCustomAuthenticator"))) {localAuthenticatorNames.contains("BasicCustomAuthenticator"))) {

Step 5. (Optional) If you need to make this as the default authenticator for all service providers, you can configure it using /repository/conf/identity/service-providers/default.xml file.

This file is configured to “BasicAuthenticator” by default.. You can modify it to “BasicCustomAuthenticator” by changing following two properties.

<Name>BasicAuthenticator</Name>
 <DisplayName>basicauth</DisplayName>

in to

<Name>BasicCustomAuthenticator</Name>
 <DisplayName>BasicCustom</DisplayName>

Step 6. Restart the server.

Step 7. You can login to management console and configure new authenticator for each service provider using “Local & Outbound Authentication Configuration”

customauth1

Step 8. Try out.

You can debug the custom authenticator source by starting server in debug mode.

>sh wso2server.sh -debug 5005

Thanks for reading…!!!

Service provider grouping with WSO2 Identity Server

$
0
0

When WSO2IS is used as SSO IDP, end user (single browser agent) can have only one SSO session with WSO2IS. This session can not be based on the service provider or tenant domain or any other parameter. It means that once end user is authenticated to WSO2IS using given SP, All other SPs which are registered with WSO2IS, would be authenticated through SSO. We can not group the SP such as there must use SSO or there must not.

But; there are some requirements which you need to achieve the SSO between only given service provider set. Lets say; there are three service providers called SP1, SP2 & SP3. Also there is another set called SP4, SP5. Your requirement would be to have SSO only between SP1/SP2/SP3 service provider group. Also, you need to have SSO between SP4/SP5 as well. Service provider grouping is not supported by the WSO2IS default. But there is some workaround to achieve it.
WSO2IS set a cookie in to the end user browser called “commonauthid”. This cookie is set for the hostname of the WSO2IS. If we can pretend the WSO2IS as two hostnames, Then two “commonauthid” cookies can be set for end user’s browser. In theory; two cookies means two SSO sessions in WSO2IS. Therefore we can create two SSO session for same end user. So; we can keep single WSO2IS node (or cluster) and expose it as different hostname using another proxy server. We can use any proxy server and we have easily configured this using virtual host configuration of Apache HTTP server.

Lets try out this.

Step 1. Install WSO2IS in your environment. Configure the SP1/SP2/SP3/SP4/SP5 in WSO2IS.

Step 2. Please make sure to configure hostname entries in the /repository/conf/carbon.xml file properly. This must be the hostname of the server which WSO2IS has installed.


<HostName>{IS_Server_Host_Name}</HostName>

<MgtHostName>{IS_Server_Host_Name}</MgtHostName>

Step 3. Configure proxy server such as Apache HTTP server with virtual host configuration. Let assume that we need to expose WSO2IS instance as two different hostname called foo.com and bar.com. Then following would be the virtual host configuration.


<IfModule mod_proxy.c>

<VirtualHost *:443>
ServerAdmin techops@wso2.com
ServerName foo.com
ServerAlias foo.com

ProxyRequests Off

SSLEngine On
SSLProxyEngine On
SSLCertificateFile /etc/apache2/credential/server.crt
SSLCertificateKeyFile /etc/apache2/credential/server.key
SSLCACertificateFile /etc/apache2/credential/ca.crt

ProxyPass / https://{IS_Server_Host_Name}:{IS_Server_Port}/
ProxyPassReverse / https://{IS_Server_Host_Name}:{IS_Server_Port}/

</VirtualHost>

<VirtualHost *:443>
ServerAdmin techops@wso2.com
ServerName bar.com
ServerAlias bar.com

ProxyRequests Off

SSLEngine On
SSLProxyEngine On
SSLCertificateFile /etc/apache2/credential/server.crt
SSLCertificateKeyFile /etc/apache2/credential/server.key
SSLCACertificateFile /etc/apache2/credential/ca.crt

ProxyPass / https://{IS_Server_Host_Name}:{IS_Server_Port}/
ProxyPassReverse / https://{IS_Server_Host_Name}:{IS_Server_Port}/

</VirtualHost>

</ifModule>

Step 4.  Configure IDP url in server providers

In one service provider group (SP1/SP2/SP3), we can configure IDP url as https://foo.com/

In other service provider group (SP4/SP5), we can configure IDP url as https://bar.com/

Step 5. Now, try out login to service provider group SP1/SP2/SP3 and verify the SSO. Also try out same for other group as well.
You can see SSO is happening only within SP1/SP2/SP3 group or within SP4/SP5 group.

Thanks for reading…!!!

Mutual SSL (X.509 Certificate) grant type for OAuth2

$
0
0

We have already discussed on implementing custom grant types for OAuth2 in this blog post. Today, we are going to implement a Mutual SSL (X.509 certificate) based grant type for WSO2IS/APIM

Following must be noted.

  1. Mutual SSL is handled at transport level. OAuth2 Grant handler has no any idea on the mutual SSL.
  2. Once mutual SSL is succeeded; Client certificate can be found from the HTTP servlet request object. If Mutual SSL is failed. it can be founded from HTTP request.
  3. Full HTTP request can not be retrieved inside grant handler and only the HTTP parameters are available inside to it.

Therefore, following solution came up.

  • Implement a custom servlet filer to retrieve the client certificate from the HTTP servlet request. If client certificate is present in the HTTP servlet request object , it meant that mutual SSL has been correctly happened. Then custom filter can retrieve it and set it as a parameter in to HTTP request object

Please find the implemented custom servlet filter source from here

  • OAuth grant handler can retrieve the client certificate from the HTTP parameter. Then grant handler can do any additional verification related to the certificate. Such as checking the CN, DN or any.

Please find the implemented sample custom grant type source from here

Lets try out above these sample implementations in WSO2IS 5.1.0 version. Please go through below steps.

Step 1. Download the servlet filter project and build using Maven3

Step 2. Copy built jar file “org.soasecurity.mutual.ssl.filter-1.0.0.jar” in to <WSO2IS_HOME>/repository/deployment/server/webapps/oauth2/WEB-INF/lib directory

Step 3. Configure following two entries under the root element of the <WSO2IS_HOME>/repository/deployment/server/webapps/oauth2/WEB-INF/web.xml file

<filter>
 <filter-name>MutualSSLFilter</filter-name>
 <filter-class>org.soasecurity.mutual.ssl.filter.MutualSSLFilter</filter-class>
 </filter>

<filter-mapping>
 <filter-name>MutualSSLFilter</filter-name>
 <url-pattern>/token</url-pattern>
 </filter-mapping>

Now we have configured the servlet filter.
Step 4. Download the x.509 grant type project and modify the certificate validation as you like. Then you can build using Maven3

Step 5. Copy built jar file “org.soasecurity.wso2.x509.grant-1.0.0.jar” in to <WSO2IS_HOME>/repository/components/lib directory

Step 6. Configure following entries in the <WSO2IS_HOME>/repository/conf/identity/identity.xml file.

<SupportedGrantType>
<GrantTypeName>x509</GrantTypeName>
<GrantTypeHandlerImplClass>org.soasecurity.wso2.oauth2.x509.grant.X509GrantHandler</GrantTypeHandlerImplClass>
<GrantTypeValidatorImplClass>org.soasecurity.wso2.oauth2.x509.grant.X509GrantValidator</GrantTypeValidatorImplClass>
</SupportedGrantType>

More details can be found from here

Step 7. Restart the server.

Step 8 Send a /token request using a client which support mutual SSL. Client must send the following query param as well.

grant_type=x509

Please try out and let us know the feedback.

Thanks for reading…!!!

Exchanging An OAuth2 Access token for An OpenAM Cookie (Cookie base OAuth2 grant)

$
0
0

OpenAM provides a set of REST APIs to authenticate the users with username/password & validates the authenticated user’s sessions.  Assume that there is an application which has been implemented to authenticate its end users by calling REST API of the OpenAM.

As an example in following HTTP POST request must be sent to OpenAM for authenticating the users.

 curl -X POST -H "X-OpenAM-Username: asela" -H "X-OpenAM-Password: mypassword" -H "Content-Type: application/json" http://localhost:8080/openam/json/realms/root/authenticate

Once user is authenticated, OpenAM will create an end user session & return the related session token as the response.

{"tokenId":"AQIC5wM2LY4Sfcx8KG5UZGwMq8mB7Pu8k2HMI1tTI2oUGLA.*AAJTSQACMDEAAlNLABQtMTkwNTYzMDM0MjY1NDM4MzA5OQACUzEAAA..*","successUrl":"/openam/console","realm":"/"}

If application needs to invoke APIs which are protected with OAuth2 on behalf of the authenticated user,  application needs to exchange this session token for OAuth2 access token.

If OAuth2 authorization server is WSO2IS or WSO2 APIM, then we can easily achieve the token exchange by implementing an OAuth2 custom grant type

You can find my previous post of implementing custom grant type for WSO2IS/WSO2APIM OAuth2 Authorization server from here.

In this post,  lets see how we can implement OpenAM cookie based grant type.

Step 1 .  Identify the way to validate the OpenAM cookie & retrieve user’s information which is related to the cookie.

This can be achieved by using following REST API which is given by OpenAM.

Curl command for REST API

curl -X POST -H "Content-Type: application/json" -H "iplanetDirectoryPro: AQIC5wM2LY4Sfcxs...EwNDU2NjE0*" http://localhost:8080/openam/json/realms/root/sessions/?_action=getSessionInfo

If cookie is valid, JSON response is returned as following.

{
"username": "asela",
"universalId": "id=asela,ou=user,dc=openam,dc=forgerock,dc=org",
"realm": "/",
"sessionHandle": "shandle:AQIC5wM2LY4Sfcxs...EwNDU2NjE0*",
"latestAccessTime": "2017-02-16T17:03:06Z",
"maxIdleExpirationTime": "2017-02-16T17:33:06Z",
"maxSessionExpirationTime": "2017-02-16T19:03:05Z"
}

Therefore we can implement this logic in our custom grant handler to validate the cookie & retrieve user’s attributes.

Step 2. Implement custom grant handler

You can find the custom grant implementation from here

You can clone it & use build using maven 3.

Step 3. Deploy & Register custom grand handler with WSO2IS & APIM.

You can copy built JAR file in to  /repository/components/lib directory.

If you are using WSO2IS, you can register it by adding following configuration in to identity.xml file under <SupportedGrantTypes> tag

<SupportedGrantType>
<GrantTypeName>openamcookie</GrantTypeName>
<GrantTypeHandlerImplClass>org.soasecurity.wso2.oauth2.openam.cookie.grant.OpenAMCookieGrantHandler</GrantTypeHandlerImplClass>
<GrantTypeValidatorImplClass>org.soasecurity.wso2.oauth2.openam.cookie.grant.OpenAMCookieGrantValidator</GrantTypeValidatorImplClass>
<OpenAMSessionEndpoint>http://localhost:8080/openam/json/realms/root/sessions/?_action=getSessionInfo</OpenAMSessionEndpoint>
</SupportedGrantType>

<OpenAMSessionEndpoint> value must be configured based on your OpenAM session info endpoint.  Default value would be following

http://localhost:8080/openam/json/realms/root/sessions/?_action=getSessionInfo

If you are using WSO2 APIM, Please change <GrantTypeHandlerImplClass> to following.

<GrantTypeHandlerImplClass>org.soasecurity.wso2.oauth2.openam.cookie.grant.ExtendedOpenAMCookieGrantHandler</GrantTypeHandlerImplClass>

After above, you need to restart the server & create an OAuth2 application using APIM store & WSO2IS management console.

You need to select new grant type as allowed grant type for created application.

Step 4. Try out.  Following is the sample HTTP POST request you need to send in to the /token endpoint.

curl -k -v -X POST -u fdowKYyXzX_01WsjSfiP0ZCgm8Ia:X_Sl5vdVtakDxCp2iWm9wKu4HAca -d "grant_type=openamcookie&cookie=AQIC5wM2LY4SfcwIW5ufU1quxPgo9l01LLH1Tlh5HQU3qi4.*AAJTSQACMDEAAlNLABM2OTUwOTk4MzY1NjMxMTUyMDA3AAJTMQAA*" https://localhost:9443/oauth2/token

In above request cookie has been send as POST body parameter with grant type parameter. Also client id/secret must be sent as basic authentication headers.

OAuth2 authorization server will repose back with access token.  Now you can exchange the cookie for an access token.

Thanks for reading!


Viewing all 15 articles
Browse latest View live