Quantcast
Channel: Sanjeewa Malalgoda's Blog
Viewing all 220 articles
Browse latest View live

WSO2 API Manager visibiity, subscription availability and relation between them

$
0
0
When we create APIs we need to aware about API visibility and subscriptions. Normally API visibility directly couple with subscription availability(simply because you cannot subscribe to something you dont see in store). See following diagram for more information about relationship between them.

Visibility - we can contorl how other users can see our APIS

Subscription availability - How other users can subscribe to APIs created by us



WSO2 API Manager - How to customize API Manager using extension points

$
0
0
Here in this article i will discuss about common extensions available in API Manager and how we can use them.


WSO2 API Manager - Troubleshoot common deployment issues

$
0
0
 Here are some of useful tips when you work on deployment related issues.

-Dsetup doesn't work for usage tables. Usage table definitions are not there in setup scripts.
In WSO2 API Manager and BAM deployment we need user manager database, registry database and api manager database. We do have setup scripts for those database under db scripts folder of product distribution.  There is no need to create any tables in stats database(manually or using setup script) as API Manager toolbox(deployed in BAM) will create them when hive queries get executed. -Dsetup option will not apply to the hive scripts inside toolbox deployed in BAM.

Understand connectivity between components in distributed API manager deployment.
This is important when you work on issues related to distributed API Manager deployment. Following steps to explain connectivity between components. It would be useful to listed them here.

1. Changed the admin password
2. Tried to log in to publisher and got the insufficient privilege error
3. Then changed the admin password in authManager element in api-manager.xml
4. Restarted and I was able to login to API publisher. Then I created an API and tried to publish. Got a permission error again.
5. Then, I changed password under API Gateway element in api-manager.xml
6. Restarted and published the API. Then, tried to invoke an API using an existing key. Got the key validation error.
7. Then, I changed the admin password in KeyManager element in api-manager.xml and all issues got resolved.

Thrift key validation does not work when we have load balancer fronted key manager.
Reason for this is most of load balancers not capable of routing traffic in session aware manner. So in such cases its always recommend to use WS key validation client.

Usage data related issues.
When you work with usage data related issues first we should check data source configurations in BAM and API Manager. Then we need to check created tables in usage database. Most of reported issues are due to configuration issues.  Same applies to billing sample as well.

How to add sub theme to API Manager jaggery applications - API Manager store/publisher customization

$
0
0
In API Manager we can add sub themes and change look and feel of jaggery applications. Here in this post i will provide high level instructions for that. Customize the existing theme and, add the new theme as a "sub theme" to the store and publisher.

(1) Navigate to "/repository/deployment/server/jaggeryapps/store/site/themes/fancy/subthemes" directory.
(2) Create a directory with the name of your subtheme. For example "test".
(3) Copy the "/repository/deployment/server/jaggeryapps/store/site/themes/fancy/css/styles-layout.css" to the new subtheme location "repository/deployment/server/jaggeryapps/store/site/themes/fancy/subthemes/test/css/styles-layout.css".
(4) At the end of the copied file add the css changes in [a].
(5) Edit "/repository/deployment/server/jaggeryapps/store/site/conf/site.json" file as below in order to make the new sub theme as the default theme.
        "theme" : {
               "base" : "fancy",
               "subtheme" : "test"
        }



Add custom css to new sub theme

.link-to-user {
  max-width: 100px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
@media only screen and (max-width:1200px){
    .navbar .nav.pull-right.login-sign-up{
        width: 100%;
        text-align:right;
float:left;
border-left:solid 1px #292e38;
border-bottom:solid 1px #292e38;
    }
.menu-content .navbar .nav.pull-right.login-sign-up > li{
        float:left;
 }
 .menu-content .navbar .nav.pull-right.login-sign-up > li.dropdown > a , .menu-content .navbar .nav.pull-right.login-sign-up > li.dropdown.open > a{
        color:#000 !important;
 }
 .search-section-custom{
         margin-top:62px;
 }
 #wrap > div.header + div.clearfix + .container-fluid{
padding-right:0;
 }
}

How monitor WSO2 server CPU usage and generate thread dump on high CPU usage using simple shell script

$
0
0
When we deployed WSO2 servers in production deployments we may need to monitor them for high CPU and memory usages. So in this article i will describe how we can use simple shell script to monitor server CPU usage and generate thread dump using jstack command.

First you need to create test.sh script using following command.
 vi test.sh 

Then paste following script content.

#!/bin/bash
# 1: ['command\ name' or PID number(,s)] 2: MAX_CPU_PERCENT
[[ $# -ne 2 ]] && exit 1
PID_NAMES=$1
# get all PIDS as nn,nn,nn
if [[ ! "$PID_NAMES" =~ ^[0-9,]+$ ]] ; then
    PIDS=$(pgrep -d ',' -x $PID_NAMES)
else
    PIDS=$PID_NAMES
fi
#  echo "$PIDS $MAX_CPU"
MAX_CPU="$2"
MAX_CPU="$(echo "($MAX_CPU+0.5)/1" | bc)"
LOOP=1
while [[ $LOOP -eq 1 ]] ; do
    sleep 0.3s
    # Depending on your 'top' version and OS you might have
    #   to change head and tail line-numbers
    LINE="$(top -b -d 0 -n 1 -p $PIDS | head -n 8 \
        | tail -n 1 | sed -r 's/[ ]+/,/g' | \
        sed -r 's/^\,|\,$//')"
    # If multiple processes in $PIDS, $LINE will only match\
    #   the most active process
    CURR_PID=$(echo "$LINE" | cut -d ',' -f 1)
    # calculate cpu limits
    CURR_CPU_FLOAT=$(echo "$LINE"| cut -d ',' -f 9)
    CURR_CPU=$(echo "($CURR_CPU_FLOAT+0.5)/1" | bc)
    echo "PID $CURR_PID: $CURR_CPU""%"
    if [[ $CURR_CPU -ge $MAX_CPU ]] ; then
        now="$(date)"
        echo "PID $CURR_PID ($PID_NAMES) went over $MAX_CPU""%" on $now
        jstack $CURR_PID > ./$now+jlog.txt
        echo "[[ $CURR_CPU""% -ge $MAX_CPU""% ]]"
        LOOP=0
        break
    fi
done
echo "Stopped"

Then we need to get process id of running WSO2 server by running following command.

sanjeewa@sanjeewa-ThinkPad-T530:~/work$ jps
30755 Bootstrap
8543 Jps
4892 Main




Now we know carbon server running with process ID 30755. Then we can start our script by providing init parameters(process ID and CPU limit). So it will keep printing CPU usage in terminal and once it reached limit it will take thread dump using jstack command. It will create new file with with embedding current date time and push Jstack output to it.

We can start scritp like this.
 sh test.sh <processId><CPU Limit>


sanjeewa@sanjeewa-ThinkPad-T530:~/work$ sh test.sh 30755 10
PID 30755: 0%
PID 30755: 0%
PID 30755: 0%
PID 30755: 0%
PID 30755 (30755) went over 10% on 2015 පෙබරවාරි 19 වැනි බ්‍රහස්පතින්දා 14:44:55 +0530
[[ 13% -ge 10% ]]
Stopped

As you can see when CPU goes above 10% it will create log file and append thread dump.

How to modify API Manager publisher to remove footer - API Manager 1.8.0

$
0
0
1. Go to publisher jaggery app (/repository/deployment/server/jaggeryapps/publisher)

2. Go to subthemes folder in publisher (site/themes/default/subthemes)

3. Create a folder with the name of your subtheme. For example "nofooter"

4. Create a folder called 'css' inside 'nofooter' folder

5. Copy the "/repository/deployment/server/jaggeryapps/publisher/site/themes/default/css/localstyles.css" to the new subtheme's css location "/repository/deployment/server/jaggeryapps/publisher/site/themes/default/subthemes/nofooter/css/"

6. Copy the "/repository/deployment/server/jaggeryapps/publisher/site/themes/default/images" folder to the new subtheme location "/repository/deployment/server/jaggeryapps/publisher/site/themes/default/subthemes/nofooter/"

7. add following css to localstyles.css file in "/repository/deployment/server/jaggeryapps/publisher/site/themes/default/subthemes/nofooter/css/" folder

#footer{
    display:none;
}

8. Edit "/repository/deployment/server/jaggeryapps/publisher/site/conf/site.json" file as below in order to make the new sub theme as the default theme.
         "theme" : {
               "base" : "default",
               "subtheme" : "nofooter"
        }

How to pass Basic authntication headers to backend server via API Manager

$
0
0

First let me explain how authorization headers work in API Manager. When user send authorization header along with API request we will use it for API authentication purpose. And we will drop it from out going message.
If you want to pass clients auth headers to back end server without dropping them at gateway you can enable following parameter and disable it.
Update following property in /repository/conf/api-manager.xml and restart server.

false

Then it will not drop user sent authorization headers at gateway. So whatever user send will go to back end as well

Send API request with Basic Auth header.

Incoming message to API gateway. As you can see we do not use API Manager authentication here. For this we can set resource auth type as none when we create API. Then send Basic auth header that need to pass back end server.
[2015-02-27 18:08:05,010] DEBUG - wire >> "GET /test-sanjeewa1/1.0.0 HTTP/1.1[\r][\n]"
[2015-02-27 18:08:05,011] DEBUG - wire >> "User-Agent: curl/7.32.0[\r][\n]"
[2015-02-27 18:08:05,011] DEBUG - wire >> "Host: 10.100.1.65:8280[\r][\n]"
[2015-02-27 18:08:05,011] DEBUG - wire >> "Accept: */*[\r][\n]"
[2015-02-27 18:08:05,011] DEBUG - wire >> "Authorization: Basic 2690b6dd2af649782bf9221fa6188[\r][\n]"
[2015-02-27 18:08:05,011] DEBUG - wire >> "[\r][\n]"

Out going message from gateway. You can see client sent Basic auth header is present in out going message
[2015-02-27 18:08:05,024] DEBUG - wire << "GET http://localhost/apim1/ HTTP/1.1[\r][\n]"
[2015-02-27 18:08:05,025] DEBUG - wire << "Authorization: Basic 2690b6dd2af649782bf9221fa6188[\r][\n]"
[2015-02-27 18:08:05,025] DEBUG - wire << "Accept: */*[\r][\n]"
[2015-02-27 18:08:05,025] DEBUG - wire << "Host: localhost:80[\r][\n]"
[2015-02-27 18:08:05,025] DEBUG - wire << "Connection: Keep-Alive[\r][\n]"
[2015-02-27 18:08:05,026] DEBUG - wire << "User-Agent: Synapse-PT-HttpComponents-NIO[\r][\n]"
[2015-02-27 18:08:05,026] DEBUG - wire << "[\r][\n]"


Other possible option is setting Basic auth headers at API gateway. For this we have 2 options.


01. Define Basic auth headers in API when you create API(see attached image). 



In API implement phase you can provide required basic auth details. Then API manager gateway will send provided authorization details as basic oauth headers to back end. Here we can let client to send Bearer token authorization header with API request. And gateway will drop it(after Bearer token validation) and pass Basic auth header to back end.

Incoming message to API gateway. Here user send Bearer token to gateway. Then gateway validate it and drop from out message.
[2015-02-27 17:36:15,580] DEBUG - wire >> "GET /test-sanjeewa/1.0.0 HTTP/1.1[\r][\n]"
[2015-02-27 17:36:15,595] DEBUG - wire >> "User-Agent: curl/7.32.0[\r][\n]"
[2015-02-27 17:36:15,595] DEBUG - wire >> "Host: 10.100.1.65:8280[\r][\n]"
[2015-02-27 17:36:15,595] DEBUG - wire >> "Accept: */*[\r][\n]"
[2015-02-27 17:36:15,595] DEBUG - wire >> "Authorization: Bearer 2690b6dd2af649782bf9221fa6188-[\r][\n]"
[2015-02-27 17:36:15,595] DEBUG - wire >> "[\r][\n]"

Out going message from gateway. You can see Basic auth header added to out going message
[2015-02-27 17:36:20,523] DEBUG - wire << "GET http://localhost/apim1/ HTTP/1.1[\r][\n]"
[2015-02-27 17:36:20,539] DEBUG - wire << "Authorization: Basic YWRtaW46YWRtaW4=[\r][\n]"
[2015-02-27 17:36:20,539] DEBUG - wire << "Accept: */*[\r][\n]"
[2015-02-27 17:36:20,540] DEBUG - wire << "Host: localhost:80[\r][\n]"
[2015-02-27 17:36:20,540] DEBUG - wire << "Connection: Keep-Alive[\r][\n]"
[2015-02-27 17:36:20,540] DEBUG - wire << "User-Agent: Synapse-PT-HttpComponents-NIO[\r][\n]"



02. This is also same as previous sample. But if need you can set API resource authorization type as none. Then client don't need to send anything in request. But APIM will add Basic auth headers to outgoing message.
You can understand message flow and headers by looking following wire log 


Incoming message to API gateway
[2015-02-27 17:37:10,951] DEBUG - wire >> "GET /test-sanjeewa/1.0.0 HTTP/1.1[\r][\n]"
[2015-02-27 17:37:10,953] DEBUG - wire >> "User-Agent: curl/7.32.0[\r][\n]"
[2015-02-27 17:37:10,953] DEBUG - wire >> "Host: 10.100.1.65:8280[\r][\n]"
[2015-02-27 17:37:10,953] DEBUG - wire >> "Accept: */*[\r][\n]"
[2015-02-27 17:37:10,953] DEBUG - wire >> "[\r][\n]"

Out going message from gateway. You can see Basic auth header is present in out going message
[2015-02-27 17:37:13,766] DEBUG - wire << "GET http://localhost/apim1/ HTTP/1.1[\r][\n]"
[2015-02-27 17:37:13,766] DEBUG - wire << "Authorization: Basic YWRtaW46YWRtaW4=[\r][\n]"
[2015-02-27 17:37:13,766] DEBUG - wire << "Accept: */*[\r][\n]"
[2015-02-27 17:37:13,766] DEBUG - wire << "Host: localhost:80[\r][\n]"
[2015-02-27 17:37:13,766] DEBUG - wire << "Connection: Keep-Alive[\r][\n]"

How to cleanup old and unused tokens in WSO2 API Manager

$
0
0
When we use WSO2 API Manager over few months we may have lot of expired, revoked and inactive tokens in IDN_OAUTH2_ACCESS_TOKEN table.
As of now we do not clear these entries for logging and audit purposes.
But with the time when table grow we may need to clear table.
Having large number of entries will slow down token generation and validation process.
So in this post we will discuss about clearing unused tokens in API Manager.

Most important thing is we should not try this with actual deployment to prevent data loss.
First take a dump of running servers database.
Then perform these instructions.
And then start server pointing to updated database and test throughly to verify that we do not have any issues.
Once you are confident with process you may schedule it for server maintenance time window.
Since table entry deletion may take considerable amount of time its advisable to test dumped data before actual cleanup task.



Stored procedure to cleanup tokens

  • Back up the existing IDN_OAUTH2_ACCESS_TOKEN table.
  • Turn off SQL_SAFE_UPDATES.
  • Delete the non-active tokens other than a single record for each state for each combination of CONSUMER_KEY, AUTHZ_USER and TOKEN_SCOPE.
  • Restore the original SQL_SAFE_UPDATES value.

USE `WSO2AM_DB`;
DROP PROCEDURE IF EXISTS `cleanup_tokens`;

DELIMITER $$
CREATE PROCEDURE `cleanup_tokens` ()
BEGIN

-- Backup IDN_OAUTH2_ACCESS_TOKEN table
DROP TABLE IF EXISTS `IDN_OAUTH2_ACCESS_TOKEN_BAK`;
CREATE TABLE `IDN_OAUTH2_ACCESS_TOKEN_BAK` AS SELECT * FROM `IDN_OAUTH2_ACCESS_TOKEN`;

-- 'Turn off SQL_SAFE_UPDATES'
SET @OLD_SQL_SAFE_UPDATES = @@SQL_SAFE_UPDATES;
SET SQL_SAFE_UPDATES = 0;

-- 'Keep the most recent INACTIVE key for each CONSUMER_KEY, AUTHZ_USER, TOKEN_SCOPE combination'
DELETE FROM IDN_OAUTH2_ACCESS_TOKEN WHERE TOKEN_STATE = 'INACTIVE' AND ACCESS_TOKEN NOT IN (SELECT ACCESS_TOKEN
FROM (SELECT * FROM IDN_OAUTH2_ACCESS_TOKEN WHERE TOKEN_STATE = 'INACTIVE' ORDER BY CONSUMER_KEY, AUTHZ_USER, TOKEN_SCOPE, TIME_CREATED DESC) x
GROUP BY CONSUMER_KEY, AUTHZ_USER, TOKEN_SCOPE);

-- 'Keep the most recent REVOKED key for each CONSUMER_KEY, AUTHZ_USER, TOKEN_SCOPE combination'
DELETE FROM IDN_OAUTH2_ACCESS_TOKEN WHERE TOKEN_STATE = 'REVOKED' AND ACCESS_TOKEN NOT IN (SELECT ACCESS_TOKEN
FROM (SELECT * FROM IDN_OAUTH2_ACCESS_TOKEN WHERE TOKEN_STATE = 'REVOKED' ORDER BY CONSUMER_KEY, AUTHZ_USER, TOKEN_SCOPE, TIME_CREATED DESC) x
GROUP BY CONSUMER_KEY, AUTHZ_USER, TOKEN_SCOPE);

-- 'Keep the most recent EXPIRED key for each CONSUMER_KEY, AUTHZ_USER, TOKEN_SCOPE combination'
DELETE FROM IDN_OAUTH2_ACCESS_TOKEN WHERE TOKEN_STATE = 'EXPIRED' AND ACCESS_TOKEN NOT IN (SELECT ACCESS_TOKEN
FROM (SELECT * FROM IDN_OAUTH2_ACCESS_TOKEN WHERE TOKEN_STATE = 'EXPIRED' ORDER BY CONSUMER_KEY, AUTHZ_USER, TOKEN_SCOPE, TIME_CREATED DESC) x
GROUP BY CONSUMER_KEY, AUTHZ_USER, TOKEN_SCOPE);

-- 'Restore the original SQL_SAFE_UPDATES value'
SET SQL_SAFE_UPDATES = @OLD_SQL_SAFE_UPDATES;

END$$
DELIMITER ;


Schedule event to run cleanup task per week
USE `WSO2AM_DB`;
DROP EVENT IF EXISTS `cleanup_tokens_event`;
CREATE EVENT `cleanup_tokens_event`
    ON SCHEDULE
      EVERY 1 WEEK STARTS '2015-01-01 00:00.00'
    DO
      CALL `WSO2AM_DB`.`cleanup_tokens`();

-- 'Turn on the event_scheduler'
SET GLOBAL event_scheduler = ON;


How to fine tune API Manager 1.8.0 to get maximum TPS and minimum response time

$
0
0

Here in this post i will discuss about API Manager 1.8.0 performance tuning. I have tested this is below mentioned deployment. Please note that this results can vary depend on you hardware server load and network. And this is not even fully optimized environment and probably you may go beyond this with better hardware+ network and configuration combination according to your use case.

Server specifications


System Information
Manufacturer: Fedora Project
Product Name: OpenStack Nova
Version: 2014.2.1-1.el7.centos

4 X CPU cores
Processor Information
Socket Designation: CPU 1
Type: Central Processor
Family: Other
Manufacturer: Bochs
Max Speed: 2000 MHz
Current Speed: 2000 MHz
Status: Populated, Enabled

Memory Device
Total Width: 64 bits
Data Width: 64 bits
Size: 8192 MB
Form Factor: DIMM
Type: RAM



Deployment Details


Deployment 01.
2 gateways (each run on dedicated machine)
2 key managers(each run on dedicated machine)
MySQL database server
1 dedicated machine to run Jmeter

Deployment 02.
1 gateways
1 key managers
MySQL database server
1 dedicated machine to run Jmeter 

 

Configuration changes.

Gateway changes.
Enable WS key validation for key management.
Edit /home/sanjeewa/work/wso2am-1.8.0/repository/conf/api-manager.xml with following configurations.
[Default value is ThriftClient]
<KeyValidatorClientType>WSClient</KeyValidatorClientType> 

[Default value is true]
<EnableThriftServer>false</EnableThriftServer
Other than this all configurations will be default configuration.
However please note that each gateway should configure to communicate key manager.

Key Manager changes.
Edit /home/sanjeewa/work/wso2am-1.8.0/repository/conf/api-manager.xml with following configurations.
[Default value is true]
<EnableThriftServer>false</EnableThriftServer> 
No need to run thrift server there as we use WS client to key validation calls.
Both gateway and key managers configured with mysql servers. For this i configured usr manager, api manager and registry database with mysql servers.

Tuning parameters applied.

Gateway nodes.

01. Change synapse configurationsAdd following entries to /home/sanjeewa/work/wso2am-1.8.0/repository/conf/synapse.properties file.
synapse.threads.core=100
synapse.threads.max=250
synapse.threads.keepalive=5
synapse.threads.qlen=1000



02. Disable HTTP access logs
Since we are testing gateway functionality here we should not much worry about http access logs. However we may need to enable this to track access. But for this deployment we assume key managers are running in DMZ and no need track http access. For gateways most of the time this does not require as we do not expose servlet ports to outside(normally we only open 8243 and 8280).
Add following entry to /home/sanjeewa/work/wso2am-1.8.0/repository/conf/log4j.properties file.

log4j.logger.org.apache.synapse.transport.http.access=OFF

Key manager nodes.

01. Disable HTTP access logs
Since we are testing gateway functionality here we should not much worry about http access logs. However we may need to enable this to track access. But for this deployment we assume key managers are running in DMZ and no need track http access.

Add following entry to /home/sanjeewa/work/wso2am-1.8.0/repository/conf/log4j.properties file.

log4j.logger.org.apache.synapse.transport.http.access=OFF


02. Change DBCP connection parameters / Datasource configurations.
There can be argument on these parameters. Specially disable validation query. But when we have high concurrency and well performing database servers we may disable this as created connections are heavily used. And on the other hand connection may work when we validate it but when we really use it connection may not work. So as per my understanding there is no issue with disabling in high concurrency scenario.

Also i added following additional parameters to optimize database connection pool.
<maxWait>60000</maxWait>
<initialSize>20</initialSize>
<maxActive>150</maxActive>
<maxIdle>60</maxIdle>
<minIdle>40</minIdle>
<testOnBorrow>false</testOnBorrow>

If you dont want to disable validation query you may use following configuration(here i increased validation interval to avoid frequent query validation).

<testOnBorrow>true</testOnBorrow>
<validationQuery>SELECT 1</validationQuery>
<validationInterval>120000</validationInterval>



03. Tuning Tomcat parameters in key manager node.
This is important because we call key validation web service service from gateway.
change following properties in this(/home/sanjeewa/work/wso2am-1.8.0/repository/conf/tomcat/catalina-server.xml) file.


Here is the brief description about changed parameters. Also i added description for each field copied from this(http://tomcat.apache.org/tomcat-7.0-doc/config/http.html) document for your reference.

I updated acceptorThreadCount to 4(default it was 2) because in my machine i have 4 cores.
However after adding this change i noticed considerable reduction of CPU usage of each core.

Increased maxThreads to 750(default value was 250)
The maximum number of request processing threads to be created by this Connector, which therefore determines the maximum number of simultaneous requests that can be handled. If not specified, this attribute is set to 200. If an executor is associated with this connector, this attribute is ignored as the connector will execute tasks using the executor rather than an internal thread pool.

Increased minSpareThreads to 250 (default value was 50)
The minimum number of threads always kept running. If not specified, the default of 10 is used.

Increased maxKeepAliveRequests to 400 (default value was 200)
The maximum number of HTTP requests which can be pipelined until the connection is closed by the server. Setting this attribute to 1 will disable HTTP/1.0 keep-alive, as well as HTTP/1.1 keep-alive and pipelining. Setting this to -1 will allow an unlimited amount of pipelined or keep-alive HTTP requests. If not specified, this attribute is set to 100.

Increased acceptCount to 400 (default value was 200)
The maximum queue length for incoming connection requests when all possible request processing threads are in use. Any requests received when the queue is full will be refused. The default value is 100.

compression="off"
Disabled compression. However this might not effective as we do not use compressed data format.



<Connector  protocol="org.apache.coyote.http11.Http11NioProtocol"
               port="9443"
               bindOnInit="false"
               sslProtocol="TLS"
               maxHttpHeaderSize="8192"
               acceptorThreadCount="2"
               maxThreads="750"
               minSpareThreads="250"
               disableUploadTimeout="false"
               enableLookups="false"
               connectionUploadTimeout="120000"
               maxKeepAliveRequests="400"
               acceptCount="400"
               server="WSO2 Carbon Server"
               clientAuth="false"
               compression="off"
               scheme="https"
               secure="true"
               SSLEnabled="true"
               compressionMinSize="2048"
               noCompressionUserAgents="gozilla, traviata"               
compressableMimeType="text/html,text/javascript,application/x-javascript,application/javascript,application/xml,text
/css,application/xslt+xml,text/xsl,image/gif,image/jpg,image/jpeg"

               URIEncoding="UTF-8"/>





Testing

Test 01 - Clustered gateway/key manager test(2 nodes)

For this test we used 10000 tokens and 150 concurrency per single gateway server. Test carried out 20 minutes to avoid caching effect on performance figures.


Round 01 with default configuration parameters
sampler_labelreq_countaveragemedian90%_lineminmaxerror%ratebandwidth
GW1310498650235014967602587.4236482276.629596
GW2315110949205302301302625.8847782310.470884
TOTAL625609550215104967605213.212664587.016218










Round 02 with http access logs disabled on key manager/ DB tuned/ with test on barrow/ synapse tuned
sampler_labelreq_countaveragemedian90%_lineminmaxerror%ratebandwidth
GW151257042220351994104271.9077093758.77817
GW253617252118330169104468.0334233931.345814
TOTAL104874292219340994108739.0580847689.347005










Round 03 with http access logs disabled/ DB tuned/ with test on barrow/ synapse tuned
sampler_labelreq_countaveragemedian90%_lineminmaxerror%ratebandwidth
GW1551192222193511425704592.3154284040.699415
GW2580467420173401190904836.3819664255.449367
TOTAL1131659621183401425709428.4774018295.955213










Round 04 with http access logs disabled/ DB tuned and removed test on barrow/ synapse tuned
sampler_labelreq_countaveragemedian90%_lineminmaxerror%ratebandwidth
GW1563604923203511496104697.2790024133.05506
GW2585450522183401033204879.4698224293.361631
TOTAL1149055422193501496109576.6028798426.288275
 


tps.png

  Results table

dsdsadsad.png

Response Time graph

Screenshot from 2015-03-18 13:07:09.png

Transactions per second graph


For 150 concurrency cluster(2 nodes) node
Average TPS - 4687.85
Average response time - 21ms
Average delay added by gateway - 16ms

Test 02 - Single gateway/key manager test


For this test we used 10000 tokens and 300 concurrency per single gateway server. Test carried out 20 minutes to avoid caching effect on performance figures.







Results table.

























Response Time Graph


For 300 concurrency 1 node
Average TPS -4956
Average response time - 55ms
Average delay added by gateway - 49ms

How to write custom handler for API Manager

$
0
0
In this(https://docs.wso2.com/display/AM180/Writing+Custom+Handlers) post we have explained how we can add handler to API Manager.

Here in this post i will add sample code required for handler. You can import this to you favorite IDE and start implementing your logic.

Please find sample code here[https://drive.google.com/file/d/0B3OmQJfm2Ft8YlRjYV96VVcxaVk/view?usp=sharing]

Dummy class would be like this. You can implement your logic there


package org.wso2.carbon.test.gateway;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.Mediator;
import org.apache.synapse.MessageContext;
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.apache.synapse.rest.AbstractHandler;
import org.apache.synapse.rest.RESTConstants;
import org.wso2.carbon.apimgt.impl.APIConstants;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

public class TestHandler extends AbstractHandler {

    private static final String EXT_SEQUENCE_PREFIX = "WSO2AM--Ext--";
    private static final String DIRECTION_OUT = "Out";
    private static final Log log = LogFactory.getLog(TestHandler.class);

    public boolean mediate(MessageContext messageContext, String direction) {
        log.info("===============================================================================");
        return true;
    }


    public boolean handleRequest(MessageContext messageContext) {
        log.info("===============================================================================");
        return true;
    }

    public boolean handleResponse(MessageContext messageContext) {
        return mediate(messageContext, DIRECTION_OUT);
    }
}

How to use API Manager Application workflow to automate token generation process

$
0
0
Workflow extensions allow you to attach a custom workflow to various operations in the API Manager such as user signup, application creation, registration, subscription etc. By default, the API Manager workflows have Simple Workflow Executor engaged in them.

The Simple Workflow Executor carries out an operation without any intervention by a workflow admin. For example, when the user creates an application, the Simple Workflow Executor allows the application to be created without the need for an admin to approve the creation process.

Sometimes we may need to do additional operations as part of work flow.
In this example we will discuss how we can generate access tokens once you finished application creation. By default you need to generate keys once you created Application in API store. With this sample that process would automate and generate access tokens for you application automatically.

You can find more information about work flows in this document.
https://docs.wso2.com/display/AM170/Adding+Workflow+Extensions


Lets first see how we can intercept workflow complete process and do something.

ApplicationCreationSimpleWorkflowExecutor.complete() method will execute after we resume workflow from BPS.
Then we can write our implementation for workflow executor and do whatever we need there.
We will have user name, application id, tenant domain and other required parameters need to trigger subscription/key generation.
If need we can directly call dao or APIConsumerImpl to generate token(call getApplicationAccessKey).
In this case we may generate tokens from workflow executor.

Here you will see code for ApplicationCreation. This class is just the same as ApplicationCreationSimpleWorkflowExecutor, but additionally generating the keys in the ApplicationCreationExecutor.complete().
In this way the token will be generated as and when the application is created.

If need user can use OAuthAdminService getOAuthApplicationDataByAppName in the BPS side using soap call to get these details. If you want to send mail with generate tokens then you can do that as well.





package test.com.apim.workflow;

import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.apimgt.api.APIManagementException;
import org.wso2.carbon.apimgt.impl.APIConstants;
import org.wso2.carbon.apimgt.impl.dao.ApiMgtDAO;
import org.wso2.carbon.apimgt.impl.dto.ApplicationWorkflowDTO;
import org.wso2.carbon.apimgt.impl.dto.WorkflowDTO;
import org.wso2.carbon.apimgt.impl.workflow.WorkflowException;
import org.wso2.carbon.apimgt.impl.workflow.WorkflowExecutor;
import org.wso2.carbon.apimgt.impl.workflow.WorkflowConstants;
import org.wso2.carbon.apimgt.impl.workflow.WorkflowStatus;

import org.wso2.carbon.apimgt.impl.dto.ApplicationRegistrationWorkflowDTO;
import org.wso2.carbon.apimgt.impl.APIManagerFactory;
import org.wso2.carbon.apimgt.api.APIConsumer;

public class ApplicationCreationExecutor extends WorkflowExecutor {

    private static final Log log =
            LogFactory.getLog(ApplicationCreationExecutor.class);

    private String userName;
    private String appName;

    @Override
    public String getWorkflowType() {
        return WorkflowConstants.WF_TYPE_AM_APPLICATION_CREATION;
    }

    /**
     * Execute the workflow executor
     *
     * @param workFlowDTO
     *            - {@link ApplicationWorkflowDTO}
     * @throws WorkflowException
     */

    public void execute(WorkflowDTO workFlowDTO) throws WorkflowException {
        if (log.isDebugEnabled()) {
            log.info("Executing Application creation Workflow..");
        }
        workFlowDTO.setStatus(WorkflowStatus.APPROVED);
        complete(workFlowDTO);

    }

    /**
     * Complete the external process status
     * Based on the workflow status we will update the status column of the
     * Application table
     *
     * @param workFlowDTO - WorkflowDTO
     */
    public void complete(WorkflowDTO workFlowDTO) throws WorkflowException {
        if (log.isDebugEnabled()) {
            log.info("Complete  Application creation Workflow..");
        }

        String status = null;
        if ("CREATED".equals(workFlowDTO.getStatus().toString())) {
            status = APIConstants.ApplicationStatus.APPLICATION_CREATED;
        } else if ("REJECTED".equals(workFlowDTO.getStatus().toString())) {
            status = APIConstants.ApplicationStatus.APPLICATION_REJECTED;
        } else if ("APPROVED".equals(workFlowDTO.getStatus().toString())) {
            status = APIConstants.ApplicationStatus.APPLICATION_APPROVED;
        }

        ApiMgtDAO dao = new ApiMgtDAO();

        try {
            dao.updateApplicationStatus(Integer.parseInt(workFlowDTO.getWorkflowReference()),status);
        } catch (APIManagementException e) {
            String msg = "Error occured when updating the status of the Application creation process";
            log.error(msg, e);
            throw new WorkflowException(msg, e);
        }

        ApplicationWorkflowDTO appDTO = (ApplicationWorkflowDTO) workFlowDTO;
        userName = appDTO.getUserName();

        appName = appDTO.getApplication().getName();

        System.out.println("UseName : " + userName + "   --- appName = " + appName) ;

        Map mapConsumerKeySecret = null ;

        try {
            APIConsumer apiConsumer = APIManagerFactory.getInstance().getAPIConsumer(userName);
            String[] appliedDomains = {""};
//Key generation
            mapConsumerKeySecret = apiConsumer.requestApprovalForApplicationRegistration(userName, appName, "PRODUCTION", "", appliedDomains, "3600");
        } catch(APIManagementException e) {
            throw new WorkflowException(
                    "An error occurred while generating token.", e);
        }

        for (Map.Entry entry : mapConsumerKeySecret.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();

            System.out.println("Key : " + key + "   ---  value = " + value);
        }
    }

    @Override
    public List getWorkflowDetails(String workflowStatus) throws WorkflowException {
        return null;
    }

}


Then add this as application creation work flow.

How to use custom passowrd validation policy in WSO2 API Manager and update sign-up UI to handle new policy

$
0
0



In this article we will discuss how we can change password policy by using identity server specific configurations. Also we will discuss about updating API store sign-up page according to custom password policy. So when new users sign-up in API store it will be easy for them.


Please follow below instructions to change password policy by updating identity-mgt.properties file available in repository/conf/security directory.
You can find more information from this url - https://docs.wso2.com/display/IS450/Password+Policies

Do necessary changes in identity-mgt.properties file
# Identity listener is enable

Identity.Listener.Enable=true

# Define password policy enforce extensions

Password.policy.extensions.1=org.wso2.carbon.identity.mgt.policy.password.DefaultPasswordLengthPolicy
Password.policy.extensions.1.min.length=6
Password.policy.extensions.1.max.length=32
Password.policy.extensions.2=org.wso2.carbon.identity.mgt.policy.password.DefaultPasswordNamePolicy
Password.policy.extensions.3=org.wso2.carbon.identity.mgt.policy.password.DefaultPasswordPatternPolicy
Password.policy.extensions.3.pattern=^((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%&*])).{0,100}$
Password should contain a digit 0-9, a lower case letter a-z, an upper case letter A-Z, one of !@#$%&* characters as in Password.policy.extensions.3.pattern. But sign-up process has issue with this pattern.

Then we can customize sign-up process of store by adding sub theme.

1. create a folder call custom-signup in below path of store. You can give any preferable name.
wso2am-1.7.0/repository/deployment/server/jaggeryapps/store/site/themes/fancy/subthemes/custom-signup

2. copy following folders to above location.
wso2am-1.7.0/repository/deployment/server/jaggeryapps/store/site/themes/fancy/js folder to wso2am-1.7.0/repository/deployment/server/jaggeryapps/store/site/themes/fancy/subthemes/custom-signup
wso2am-1.7.0/repository/deployment/server/jaggeryapps/store/site/themes/fancy/templates folder to wso2am-1.7.0/repository/deployment/server/jaggeryapps/store/site/themes/fancy/subthemes/custom-signup

3. Open below file. This file contain password validation function of sign-up.
wso2am-1.7.0/repository/deployment/server/jaggeryapps/store/site/themes/fancy/subthemes/custom-signup/js/lib/jquery.validate.password.js
You can change validation logic written in $.validator.passwordRating = function(password, username) according to above pattern or whatever pattern you want.

Please find the attach jquery.validate.password.js modified according to above pattern.

(function($) {
       function rating(rate, message) {
        return {
            rate: rate,
            messageKey: message
        };
    }
    function uncapitalize(str) {
        return str.substring(0, 1).toLowerCase() + str.substring(1);
    }
    $.validator.passwordRating = function(password, username) {
            var minLength = 6;
            var passwordStrength   = 0;
            if ((password.length >0) && (password.length <=5)) passwordStrength=0;
            if (password.length >= minLength) passwordStrength++;
            if ((password.match(/[a-z]/)) && (password.match(/[A-Z]/))) passwordStrength++;
            if (password.match(/\d+/)) passwordStrength++;
            if (password.match(/.[!,@,#,$,%,^,&,*]/)) passwordStrength++;
            if (password.length > 12) passwordStrength++;
            if (username && password.toLowerCase()== username.toLowerCase()){
                passwordStrength = 0;
            }
            $('#pwdMeter').removeClass();
            $('#pwdMeter').addClass('neutral');
            switch(passwordStrength){
            case 1:
                return rating(1, "very-weak");
              break;
            case 2:
                return rating(2, "weak");
              break;
            case 3:
                return rating(3, "weak");
              break;
            case 4:
                return rating(4, "medium");
              break;
            case 5:
                 return rating(5, "strong");
              break;
            case 6:
                 return rating(5, "vstrong");
              break;
            default:
                return rating(1, "very-weak");
            }
    }
    $.validator.passwordRating.messages = {
        "similar-to-username": "Too similar to username",
        "very-weak": "Very weak",
        "weak": "Weak",
        "medium": "Medium",
        "strong": "Strong",
        "vstrong": "Very Strong"
    }
    $.validator.addMethod("password", function(value, element, usernameField) {
        // use untrimmed value
        var password = element.value,
        // get username for comparison, if specified
            username = $(typeof usernameField != "boolean" ? usernameField : []);        
        var rating = $.validator.passwordRating(password, username.val());
        // update message for this field
        var meter = $(".password-meter", element.form);
        meter.find(".password-meter-bar").removeClass().addClass("password-meter-bar").addClass("password-meter-" + rating.messageKey);
        meter.find(".password-meter-message")
        .removeClass()
        .addClass("password-meter-message")
        .addClass("password-meter-message-" + rating.messageKey)
        .text($.validator.passwordRating.messages[rating.messageKey]);
        // display process bar instead of error message
        return rating.rate > 3;
    }, "Minimum system requirements not met");
    // manually add class rule, to make username param optional
    $.validator.classRuleSettings.password = { password: true };
   
})(jQuery);


Also make sure return rating.rate > 2; of $.validator.addMethod("password", function(value, element, usernameField) will give you the expected strength of password.
We can modify it as follows
return rating.rate > 3;
to make sure that entered password adhere above pattern.


4. If you want to change above Password.policy.extensions.3.pattern to different pattern and show password tips to user when entering password you need to edit following files.
wso2am-1.7.0/repository/deployment/server/jaggeryapps/store/site/themes/fancy/subthemes/custom-signup/templates/user/sign-up/template.jag

Below section correspond to display password tips
  
<div class="help-block" id="password-help" style="display:none">
 <%=i18n.localize("pwHelpMsgLine1")%>
 <ul>
 <li>This is new changed TIP :-) </li>               
<li><%=i18n.localize("pwHelpMsgLine3")%></li>
<li><%=i18n.localize("pwHelpMsgLine4")%></li>
<li><%=i18n.localize("pwHelpMsgLine5")%></li>
</ul>
</div>
                 
  
Please change text as you want in below file accroding to above keys.
wso2am-1.7.0/repository/deployment/server/jaggeryapps/store/site/conf/locales/jaggery/locale_default.json


5. Finally, do below config to enable above changes in store.
Open wso2am-1.7.0/repository/deployment/server/jaggeryapps/store/site/conf/site.json
Add sub theme folder as below
    "theme" : {
        "base" : "fancy",
        "subtheme" : "custom-signup"
    },
  
This will override current user sign-up validation by above changes as well as if you did any changes to password tips text.

How to generate custom error message with custom http status code for throttled out messages in WSO2 API Manager.

$
0
0
In this post we will discuss how we can override the throttle out message HTTP status code. APIThrottleHandler.handleThrottleOut method indicates that the _throttle_out_handler.xml sequence is executed if it exists. If you need to send custom message with custom http status code we may execute additional sequence which can generate new error message. There we can override message body, http status code etc.

Update _throttle_out_handler_.xml as follows.

<?xmlversion="1.0"encoding="UTF-8"?>
<sequencexmlns="http://ws.apache.org/ns/synapse"name="_throttle_out_handler_">
<propertyname="X-JWT-Assertion"scope="transport"action="remove"/>
   <sequencekey="convert"/>
   <drop/>
</sequence>

Then add new sequence named convert.xml

  <sequencename="convert">
      <sequencekey="_build_"/>
            <payloadFactorymedia-type="xml">
               <format>
                  <am:faultxmlns:am="http://wso2.org/apimanager">
                     <am:code>$1</am:code>
                     <am:type>Status report</am:type>
                     <am:message>Runtime Error</am:message>
                     <am:description>$2</am:description>
                  </am:fault>
               </format>
               <args>
                  <argevaluator="xml"expression="$ctx:ERROR_CODE"/>
                  <argevaluator="xml"expression="$ctx:ERROR_MESSAGE"/>
               </args>
            </payloadFactory>
      <propertyname="RESPONSE"value="true"/>
      <headername="To"action="remove"/>
      <propertyname="HTTP_SC"value="555"scope="axis2"/>
      <propertyname="NO_ENTITY_BODY"scope="axis2"action="remove"/>
      <propertyname="ContentType"scope="axis2"action="remove"/>
      <propertyname="Authorization"scope="transport"action="remove"/>
      <propertyname="Host"scope="transport"action="remove"/>
      <propertyname="Accept"scope="transport"action="remove"/>
      <propertyname="X-JWT-Assertion"scope="transport"action="remove"/>
      <propertyname="messageType"value="application/json"scope="axis2"/>
      <send/>
   </sequence>

How to use cxf intercepter to pre-process requests to JAX-RS services - Apply security for JAX_RS services

$
0
0

When we use jax-rs services sometimes we need to add request pre processors to services. In this post i will discuss how we can use cxf interceptor in jax-rs services.
You may find more information from this url[http://cxf.apache.org/docs/interceptors.html]
package demo.jaxrs.server;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;

public class CustomOutInterceptor extends AbstractPhaseInterceptor {

    public CustomOutInterceptor() {
        //We will use PRE_INVOKE phase as we need to process message before hit actual service
        super(Phase.PRE_INVOKE );
    }

    public void handleMessage(Message outMessage) {
        System.out.println("Token: "+ ((TreeMap) outMessage.get(Message.PROTOCOL_HEADERS)).get("Authorization"));
  // Do your processing with Authorization transport header.
    }
}

Then we need to register Interceptor by adding entry to webapp/WEB-INF/cxf-servlet.xml file. Then it will execute before request dispatch to actual service.


<beansxmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jaxrs="http://cxf.apache.org/jaxrs"
       xsi:schemaLocation="
         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
         http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd"
>
    <jaxrs:serverid="APIService"address="/">
        <jaxrs:serviceBeans>
            <refbean="serviceBean"/>
        </jaxrs:serviceBeans>

        <jaxrs:inInterceptors>
            <refbean="testInterceptor"/>
        </jaxrs:inInterceptors>
    </jaxrs:server>
    <beanid="testInterceptor"class="demo.jaxrs.server.CustomOutInterceptor" />
    <beanid="serviceBean"class="demo.jaxrs.server.APIService"/>
</beans>




Then compile web app and deploy in application server. Once you send request with Authorization header you will noticed that it printed in server logs.

See following sample curl request
curl -k -v -H "Authorization: Bearer d5701a8ed6f677f215fa4d65c05e361" http://127.0.0.1:9763/APIManager/qqqq-1.0.0-admin/

And server logs for request
Token: [Bearer d5701a8ed6f677f215fa4d65c05e361]
API Service -- invoking getAPI, API id is: qqqq-1.0.0-admin

WSO2 API Manager relationship between timestamp skew, token validity period and cache expiration time

$
0
0
Let me explain how time stamp skew works and how it effect to token generation.
First  time stamp skew is there to fix the issues due to small time differences in system clock values of servers. 
Let say you have 2 key managers and you generate token from one and authenticate with other. 
When first key manager generates token(say life span is 3600sec), time stamp skew value(say 300sec) will be deducted from token life time(client will notify that 3300sec is token validity period). 
Then he call to second key manager with that token exactly after 3200 secs and there is time different between key managers(second key manager has +300 sec time difference). 
In such cases time stamp skew will take care of those small gaps.

So theoretically 
time stamp skew should never large than token life time
It should be very small comparing to token validity period.
Token cache duration should never large than token validity period.

You can change configuration values according to requirements but you cannot put any random numbers as you need because those are inter related :-)

Configure Categorization APIs for store via Tabs

$
0
0
If you want to see the APIs grouped according to different topics in the API Store, do the following:
Go to /repository/deployment/server/jaggeryapps/store/site/conf directory, open the site.json file and set the tagWiseMode attribute as true.
Go to the API Publisher and add tags with the suffix "-group" to APIs (e.g., Workflow APIs-group, Integration APIs-group, Quote APIs-group.)
Restart the server.
After you publish the APIs, you see the APIs listed under their groups. You can click on a group to check what the APIs are inside it.
If you want to have any other name for group you can do that as well. For that you need to change following property in site.json configuration file.
"tagGroupKey" :"-group",
Here -group can replace with any postfix you like.



Use OpenID with OAuth 2.0 in WSO2 API Manager to retrieve user information

$
0
0
When we calling an API, we request for an oauth token which we send with each API call.

The reason for introduce OpenID on top of oauth 2.0 is oauth 2.0 access token retrieving process does not provide any additional information about user who is generating access tokens. Oauth is only authorization mechanism and we cannot derive more information from that.
So to get more information about user we will use openId scope to get user access token.

In that case we will pass openid scope with token request. Then we will get JWT as a part of the response from API manager, which contains user information in addition to access token and refresh token. So we will have all required user information after token generation process and we can use it for next steps if we need to do anything specific to users.
Issue following command to request OpenID based OAuth token.

Request
curl -k -d "grant_type=password&username=admin&password=admin&scope=openid" -H "Authorization: Basic M1J6RFNrRFI5ZmQ5czRqY296R2xfVjh0QU5JYTpXeElqSkFJd0dqRWVYOHdHZGFfcGM1Wl94RjRh, Content-Type: application/x-www-form-urlencoded" https://apiw.test.com/token

Response

{"scope":"openid","token_type":"Bearer","expires_in":3600,
"refresh_token":"65af3dbea3294b1524832d3869361e3e",
"id_token":"eyJhbGciOiJSUzI1NiJ9.eyJhdXRoX3RpbWUiOjE0MzA0NTY4MzM5OTgsImV4cCI6MTQzMDQ2MDQzNDAxNCwic3ViIjoiYWRtaW5AY2FyYm9uLnN1cGVyIiwiYXpwIjoiM1J6RFNrRFI5ZmQ5czRqY296R2xfVjh0QU5JYSIsImF0X2hhc2giOiJNV013WXpreVl6UmxPVGhsTkRNM01XTTVNVFEyTTJWbE0yWXlNamcwWXc9PSIsImF1ZCI6WyIzUnpEU2tEUjlmZDlzNGpjb3pHbF9WOHRBTklhIl0sImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTQ0M1wvb2F1dGgyZW5kcG9pbnRzXC90b2tlbiIsImlhdCI6MTQzMDQ1NjgzNDAxNH0.Fc4DO8A22euo04vnBoE87RVBtDQ-73Z2hNZ8_WpeKslkumhEuUVcf6y03D5HZBlGDUi8zC1SUHewg4WEE8HvI6wA59wp8BErK6pY3Zb02pWbJsPh7VBHwky2g5PtvKSsGiy0rd2tuehY-_dAy7LBKNSUOhkmGdLXkSSThuIQxKOHDAJKHCY4I_36B9OH1scs34EG9MKG4vSNdfdcf4mSg0KUD98Jdw_NS-T4pRZK_sCeT-1BBodYEabEVREHxfcDr7BGYugMiiWThVUzd4WIHD83bVwxXP17POzuo6dS_l78pBWZtBBMPKXqhd9VMNZpc-sR07DS7KkHoV6Fp3l0oA",
"access_token":"1c0c92c4e98e4371c91463ee3f2284c"}

This response contain JWT as well. Then we can invoke user info API as follows.
curl -k -v -H "Authorization: Bearer 1c0c92c4e98e4371c91463ee3f2284c" https://km.test.com/oauth2/userinfo?schema=openid

HTTP/1.1 200 OK
{"email":"sanjeewa@wso2.com","family_name":"malalgoda"}As you see we can get results for user details.

WSO2 API Manager - Decode JWT in ESB using JWT Decode mediator.

$
0
0
Here in this post i will list code for JWTDecorder mediator which we can use in WSO2 ESB or any other synapse based WSO2 product to decode JWT header.

After message gone through this mediator, all claims in JWT will present in message context as properties. Property name will be claim name. So you can use them in rest of the mediation flow.

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.axiom.util.base64.Base64Utils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.oltu.oauth2.jwt.JWTException;
import org.apache.oltu.oauth2.jwt.JWTProcessor;
import org.apache.synapse.ManagedLifecycle;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseException;
import org.apache.synapse.SynapseLog;
import org.apache.synapse.core.SynapseEnvironment;
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.apache.synapse.mediators.AbstractMediator;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.core.util.KeyStoreManager;



public class JWTDecoder extends AbstractMediator implements ManagedLifecycle {
   
    private static Log log = LogFactory.getLog(JWTDecoder.class);

    private final String CLAIM_URI = "http://wso2.org/claims/";
    private final String SCIM_CLAIM_URI = "urn:scim:schemas:core:1.0:";

    private KeyStore keyStore;

    public void init(SynapseEnvironment synapseEnvironment) {
        if (log.isInfoEnabled()) {
            log.info("Initializing JWTDecoder Mediator");
        }
        String keyStoreFile = "";
        String password = "";

        try {
            keyStore = KeyStore.getInstance("JKS");
        } catch (KeyStoreException e) {
            //throw new Exception("Unable to get JKS KeyStore instance");
        }
        char[] storePass = password.toCharArray();

        // load the key store from file system
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(keyStoreFile);
            keyStore.load(fileInputStream, storePass);
            fileInputStream.close();
        } catch (FileNotFoundException e) {
            if (log.isErrorEnabled()) {
                log.error("Error loading keystore", e);
            }
        } catch (NoSuchAlgorithmException e) {
            if (log.isErrorEnabled()) {
                log.error("Error loading keystore", e);
            }
        } catch (CertificateException e) {
            if (log.isErrorEnabled()) {
                log.error("Error loading keystore", e);
            }
        } catch (IOException e) {
            if (log.isErrorEnabled()) {
                log.error("Error loading keystore", e);
            }
        }
    }

    public boolean mediate(MessageContext synapseContext) {
        SynapseLog synLog = getLog(synapseContext);

        if (synLog.isTraceOrDebugEnabled()) {
            synLog.traceOrDebug("Start : JWTDecoder mediator");
            if (synLog.isTraceTraceEnabled()) {
                synLog.traceTrace("Message : " + synapseContext.getEnvelope());
            }
        }

        // Extract the HTTP headers and then extract the JWT from the HTTP Header map
        org.apache.axis2.context.MessageContext axis2MessageContext = ((Axis2MessageContext) synapseContext).getAxis2MessageContext();
        Object headerObj = axis2MessageContext.getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
        @SuppressWarnings("unchecked")
        Map headers = (Map) headerObj;
        String jwt_assertion = (String) headers.get("x-jwt-assertion");

        // Incoming request does not contain the JWT assertion
        if (jwt_assertion == null || jwt_assertion == "") {
            // Since this is an unauthorized request, send the response back to client with 401 - Unauthorized error
            synapseContext.setTo(null);
            synapseContext.setResponse(true);
            axis2MessageContext.setProperty("HTTP_SC", "401");
            // Log the authentication failure
            String err = "JWT assertion not found in the message header";
            handleException(err, synapseContext);
            return false;
        }

        boolean isSignatureVerified = verifySignature(jwt_assertion, synapseContext);

        try {
            if (isSignatureVerified) {
                // Process the JWT, extract the values and set them to the Synapse environment
                if (log.isDebugEnabled()){
                    log.debug("JWT assertion is : "+jwt_assertion);
                }
                JWTProcessor processor = new JWTProcessor().process(jwt_assertion);
                Map claims = processor.getPayloadClaims();
                for (Map.Entry claimEntry : claims.entrySet()) {
                    // Extract the claims and set it in Synapse context
                    if (claimEntry.getKey().startsWith(CLAIM_URI)) {
                        String tempPropName = claimEntry.getKey().split(CLAIM_URI)[1];
                        synapseContext.setProperty(tempPropName, claimEntry.getValue());
                        if(log.isDebugEnabled()){
                            log.debug("Getting claim :"+tempPropName+" , " +claimEntry.getValue() );
                        }
                    } else if (claimEntry.getKey().startsWith(SCIM_CLAIM_URI)) {
                        String tempPropName = claimEntry.getKey().split(SCIM_CLAIM_URI)[1];
                        if (tempPropName.contains(".")) {
                            tempPropName = tempPropName.split("\\.")[1];
                        }

                        synapseContext.setProperty(tempPropName, claimEntry.getValue());
                        if(log.isDebugEnabled()){
                            log.debug("Getting claim :"+tempPropName+" , " +claimEntry.getValue() );
                        }
                    }
                }
            } else {
                return false;
            }
        } catch (JWTException e) {
            log.error(e.getMessage(), e);
            throw new SynapseException(e.getMessage(), e);
        }

        if (synLog.isTraceOrDebugEnabled()) {
            synLog.traceOrDebug("End : JWTDecoder mediator");
        }
       
        return true;
    }

    private boolean verifySignature(String jwt_assertion, MessageContext synapseContext) {
        boolean isVerified = false;
        String[] split_string = jwt_assertion.split("\\.");
        String base64EncodedHeader = split_string[0];
        String base64EncodedBody = split_string[1];
        String base64EncodedSignature = split_string[2];

        String decodedHeader = new String(Base64Utils.decode(base64EncodedHeader));
        byte[] decodedSignature = Base64Utils.decode(base64EncodedSignature);
        Pattern pattern = Pattern.compile("^[^:]*:[^:]*:[^:]*:\"(.+)\"}$");
        Matcher matcher = pattern.matcher(decodedHeader);
        String base64EncodedCertThumb = null;
        if (matcher.find()) {
            base64EncodedCertThumb = matcher.group(1);
        }
        byte[] decodedCertThumb = Base64Utils.decode(base64EncodedCertThumb);

        Certificate publicCert = null;


        publicCert = getSuperTenantPublicKey(decodedCertThumb, synapseContext);
        try {
            if (publicCert != null) {
                isVerified = verifySignature(publicCert, decodedSignature, base64EncodedHeader, base64EncodedBody,
                        base64EncodedSignature);
            } else if (!isVerified) {
                publicCert = getTenantPublicKey(decodedCertThumb, synapseContext);
                if (publicCert != null) {
                    isVerified = verifySignature(publicCert, decodedSignature, base64EncodedHeader, base64EncodedBody,
                            base64EncodedSignature);
                } else {
                    throw new Exception("Couldn't find a public certificate to verify signature");
                }

            }

        } catch (Exception e) {
            handleSigVerificationException(e, synapseContext);
        }
        return isVerified;
    }

    private Certificate getSuperTenantPublicKey(byte[] decodedCertThumb, MessageContext synapseContext){
        String alias = getAliasForX509CertThumb(keyStore, decodedCertThumb, synapseContext);
        if (alias != null) {
            // get the certificate associated with the given alias from
            // default keystore
            try {
                return keyStore.getCertificate(alias);
            } catch (KeyStoreException e) {
                if (log.isErrorEnabled()) {
                    log.error("Error when getting server public certificate: " , e);
                }
            }
        }
        return null;
    }

    private Certificate getTenantPublicKey(byte[] decodedCertThumb, MessageContext synapseContext){
        SynapseLog synLog = getLog(synapseContext);

        int tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId();
        String tenantDomain = CarbonContext.getThreadLocalCarbonContext().getTenantDomain();
       
        if (synLog.isTraceOrDebugEnabled()) {
            synLog.traceOrDebug("Tenant Domain: " + tenantDomain);
        }

        KeyStore tenantKeyStore = null;
        KeyStoreManager tenantKSM = KeyStoreManager.getInstance(tenantId);
        String ksName = tenantDomain.trim().replace(".", "-");
        String jksName = ksName + ".jks";
        try {
            tenantKeyStore = tenantKSM.getKeyStore(jksName);
        } catch (Exception e) {
            if (log.isErrorEnabled()) {
                log.error("Error getting keystore for " + tenantDomain, e);
            }
        }
        if (tenantKeyStore != null) {
            String alias = getAliasForX509CertThumb(tenantKeyStore, decodedCertThumb, synapseContext);
            if (alias != null) {
                // get the certificate associated with the given alias
                // from
                // tenant's keystore
                try {
                    return tenantKeyStore.getCertificate(alias);
                } catch (KeyStoreException e) {
                    if (log.isErrorEnabled()) {
                        log.error("Error when getting tenants public certificate: " + tenantDomain, e);
                    }
                }
            }
        }

        return null;
    }
   
    private boolean verifySignature(Certificate publicCert, byte[] decodedSignature, String base64EncodedHeader,
            String base64EncodedBody, String base64EncodedSignature) throws NoSuchAlgorithmException,
            InvalidKeyException, SignatureException {
        // create signature instance with signature algorithm and public cert,
        // to verify the signature.
        Signature verifySig = Signature.getInstance("SHA256withRSA");
        // init
        verifySig.initVerify(publicCert);
        // update signature with signature data.
        verifySig.update((base64EncodedHeader + "." + base64EncodedBody).getBytes());
        // do the verification
        return verifySig.verify(decodedSignature);
    }

    private String getAliasForX509CertThumb(KeyStore keyStore, byte[] thumb, MessageContext synapseContext) {
        SynapseLog synLog = getLog(synapseContext);
        Certificate cert = null;
        MessageDigest sha = null;

        try {
            sha = MessageDigest.getInstance("SHA-1");
        } catch (NoSuchAlgorithmException e) {
            handleSigVerificationException(e, synapseContext);
        }
        try {
            for (Enumeration e = keyStore.aliases(); e.hasMoreElements();) {
                String alias = e.nextElement();
                Certificate[] certs = keyStore.getCertificateChain(alias);
                if (certs == null || certs.length == 0) {
                    // no cert chain, so lets check if getCertificate gives us a result.
                    cert = keyStore.getCertificate(alias);
                    if (cert == null) {
                        return null;
                    }
                } else {
                    cert = certs[0];
                }
                if (!(cert instanceof X509Certificate)) {
                    continue;
                }
                sha.reset();
                try {
                    sha.update(cert.getEncoded());
                } catch (CertificateEncodingException e1) {
                    //throw new Exception("Error encoding certificate");
                }
                byte[] data = sha.digest();
                if (new String(thumb).equals(hexify(data))) {
                    if (synLog.isTraceOrDebugEnabled()) {
                        synLog.traceOrDebug("Found matching alias: " + alias);
                    }
                    return alias;
                }
            }
        } catch (KeyStoreException e) {
            if (log.isErrorEnabled()) {
                log.error("Error getting alias from keystore", e);
            }
        }
        return null;
    }

    private String hexify(byte bytes[]) {
        char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7',
                            '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

        StringBuffer buf = new StringBuffer(bytes.length * 2);

        for (int i = 0; i < bytes.length; ++i) {
            buf.append(hexDigits[(bytes[i] & 0xf0) >> 4]);
            buf.append(hexDigits[bytes[i] & 0x0f]);
        }

        return buf.toString();
    }

    private void handleSigVerificationException(Exception e, MessageContext synapseContext) {
        synapseContext.setTo(null);
        synapseContext.setResponse(true);
        org.apache.axis2.context.MessageContext axis2MessageContext = ((Axis2MessageContext) synapseContext).getAxis2MessageContext();
        axis2MessageContext.setProperty("HTTP_SC", "401");
        String err = e.getMessage();
        handleException(err, synapseContext);
    }

    public void destroy() {
        if (log.isInfoEnabled()) {
            log.info("Destroying JWTDecoder Mediator");
        }
    }
}





Update key stores in WSO2 API Manager and Identity server(production recommendation )

$
0
0

Here in this post i will discuss how we can generate keystores and include them to WSO2 products before they deploy in production deployment.

Most of organizations have their own crt file and keys. So lets see how we can use them to create certificates required for WSO2 servers and deploy them.

Step 1: Follow follow steps to update the keystore.

openssl pkcs12 -export -in /etc/pki/tls/certs/sanjeewa-com.crt -inkey /etc/pki/tls/private/private-key.key -name "wso2sanjeewa" -certfile /etc/pki/tls/certs/DigiCertCA.crt -out wso2sanjeewa.pfx
Enter Export Password:
Verifying - Enter Export Password:

/usr/java/default/bin/keytool -importkeystore -srckeystore wso2sanjeewa.pfx -srcstoretype pkcs12 -destkeystore wso2sanjeewa.jks -deststoretype JKS
Enter destination keystore password: 
Re-enter new password:
Enter source keystore password: 
Entry for alias wso2sanjeewa successfully imported.
Import command completed:  1 entries successfully imported, 0 entries failed or cancelled

 /usr/java/default/bin/keytool -export -alias wso2sanjeewa -keystore wso2sanjeewa.jks -file wso2sanjeewa.pem
Enter keystore password: 
Certificate stored in file

/usr/java/default/bin/keytool -import -alias wso2sanjeewa -file /opt/o2/wso2sanjeewa.pem -keystore client-truststore.jks -storepass wso2carbon
Certificate was added to keystore

Now we have all files we need to copy.

Step 2: We need to copy them to /opt/o2/WSO2Servers/wso2am-1.8.0/repository/resources/security
This file path change with product you use(normally its /repository/resources/security ).
We need to do this for all products deployed in this deployment.

Step 3: Then after that you need to find all occurrence of wso2carbon.jks( grep -rnw 'wso2carbon.jks') and replace them with wso2sanjeewa.jks file we generated in above steps.

Step 4:Then search for alias(grep -rnw 'alias') and KeyAlias(grep -rnw 'KeyAlias') in all files in wso2Server. If you found them as wso2carbon then only replace it with wso2sanjeewa.

We need to follow these steps very carefully. Here we have listed filenames and changed parameters for your reference.

While you carry out step 3 and 4 for IS you need to change following files and lines

repository/conf/security/application-authentication.xml:96:         
<Parameter name="TrustStorePath">/repository/resources/security/wso2sanjeewa.jks</Parameter>

repository/conf/identity.xml:244:              
<Location>${carbon.home}/repository/resources/security/wso2sanjeewa.jks</Location>

repository/conf/carbon.xml:302:           
<Location>${carbon.home}/repository/resources/security/wso2sanjeewa.jks</Location>

repository/conf/carbon.xml:308:           
<KeyAlias>wso2sanjeewa</KeyAlias>

repository/conf/carbon.xml:318:           
<Location>${carbon.home}/repository/resources/security/wso2sanjeewa.jks</Location>

repository/conf/carbon.xml:324:          
<KeyAlias>wso2sanjeewa</KeyAlias>




While you carry out step 3 and 4 for APIM you need to change following files and lines

repository/deployment/server/jaggeryapps/store/site/conf/site.json:14:       
"identityAlias":"wso2sanjeewa",

repository/deployment/server/jaggeryapps/store/site/conf/site.json:16:       
"keyStoreName":"/opt/o2/WSO2Servers/wso2am-1.8.0/repository/resources/security/wso2sanjeewa.jks"

repository/deployment/server/jaggeryapps/publisher/site/conf/site.json:14:       
"identityAlias":"wso2sanjeewa",

repository/deployment/server/jaggeryapps/publisher/site/conf/site.json:16:       
"keyStoreName":"/opt/o2/WSO2Servers/wso2am-1.8.0/repository/resources/security/wso2sanjeewa.jks"

repository/conf/security/secret-conf.properties:21:
keystore.identity.location=repository/resources/security/wso2sanjeewa.jks

repository/conf/security/secret-conf.properties:23:
keystore.identity.alias=wso2sanjeewa

repository/conf/security/secret-conf.properties:32:
keystore.trust.alias=wso2sanjeewa

repository/conf/carbon.xml:313:           
<Location>${carbon.home}/repository/resources/security/wso2sanjeewa.jks</Location>

repository/conf/carbon.xml:319:           
<KeyAlias>wso2sanjeewa</KeyAlias>

repository/conf/carbon.xml:329:           
<Location>${carbon.home}/repository/resources/security/wso2sanjeewa.jks</Location>

repository/conf/carbon.xml:335:           
<KeyAlias>wso2sanjeewa</KeyAlias>



How to add custom message to WSO2 API Publisher - Show custom message based on user input or selections

$
0
0
In API Manager we can add sub themes and change look and feel of jaggery applications.
Please follow below instructions. Here i have listed instructions to add new warning message when you changed API visibility.

(1) Navigate to "/repository/deployment/server/jaggeryapps/publisher/site/themes/default/subthemes" directory.
(2) Create a directory with the name of your sub theme. For example "new-theme".
(3) Copy /repository/deployment/server/jaggeryapps/publisher/site/themes/default/templates/item-design/template.jag to the new subtheme location "/repository/deployment/server/jaggeryapps/publisher/site/themes/default/subthemes/new-theme/templates/item-design/template.jag".

(4) Go to template.jag file in sub themes directory we created and find following code block.

        $('#visibility').change(function(){
            var visibility = $('#visibility').find(":selected").val();
            if (visibility == "public" || visibility == "private" || visibility == "controlled"){
                $('#rolesDiv').hide();
            } else{
                $('#rolesDiv').show();
            }
        });
Then change it as follows. You can change message if need.

        $('#visibility').change(function(){
            var visibility = $('#visibility').find(":selected").val();

            if (visibility != "public"){
            jagg.message({content:"You have changed visibility of API, so based on visibility subscription availability may change",type:"warn"});
            }

            if (visibility == "public" || visibility == "private" || visibility == "controlled"){
                $('#rolesDiv').hide();
            } else{
                $('#rolesDiv').show();
            }
        });



(5) Edit "/repository/deployment/server/jaggeryapps/publisher/site/conf/site.json" file as below in order to make the new sub theme as the default theme.
        "theme" : {
               "base" : "fancy",
                "subtheme" : "new-theme"
        }



Here i have listed generic instructions to change this using sub themes.
Viewing all 220 articles
Browse latest View live