Usage
Customer Generation and Usage
See Bearer token for instructions on setting up user to access apis. https://sunwavehealth.atlassian.net/wiki/spaces/~63696606d60bd2b365f87223/pages/78938113/OAUTH+2+Access+Code+Auth+Flow+for+Rest+APIs#Generate-Access-Code
Generate Digest Example Code
package com.sunwave; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.util.Base64; import java.util.TimeZone; import java.util.Date; import org.apache.commons.codec.binary.Hex; public class DigestCreator { private String userId; private String clientId; private String clientSecret; // Realm Private Key "select client_secret from sw_external_application where client_id = ? and clinic_id = ? private String clinicId; private String transactionId; //"select id from sw_api_transaction where transaction_id = ? and clinic_id = ?" private String payload; private String createMd5Digest() throws NoSuchAlgorithmException, UnsupportedEncodingException { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(payload.getBytes()); return Hex.encodeHexString(md.digest()); } private String createSeed() throws NoSuchAlgorithmException, UnsupportedEncodingException { String seed = null; if (payload == null) { seed = userId + ":" + clientId + ":" + getDateTimeBase64() + ":" + clinicId + ":" + transactionId; } else { seed = userId + ":" + clientId + ":" + getDateTimeBase64() + ":" + clinicId + ":" + transactionId + ":" + createMd5Digest(); } return seed; } private String getDateTimeBase64() throws UnsupportedEncodingException { SimpleDateFormat df = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z"); df.setTimeZone(TimeZone.getTimeZone("GMT")); Date d = new Date(); java.sql.Timestamp now = new java.sql.Timestamp(new java.util.Date().getTime()); String dateTime = df.format(now); byte[] encodedDate = Base64.getEncoder().encode(dateTime.getBytes()); return new String(encodedDate, "UTF8"); } private String createTransactionId() { return null; } private String createToken() throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException { if (payload == null) return userId + ":" + clientId + ":" + getDateTimeBase64() + ":" + clinicId + ":" + transactionId + ":" + createHMAC(createSeed()); else return userId + ":" + clientId + ":" + getDateTimeBase64() + ":" + clinicId + ":" + transactionId + ":" + createMd5Digest() + ":" + createHMAC(createSeed()); } private String createHMAC(String message) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException { byte[] byteKey = clientSecret.getBytes("UTF-8"); final String HMAC_SHA512 = "HmacSHA512"; Mac sha512_HMAC = Mac.getInstance(HMAC_SHA512); SecretKeySpec keySpec = new SecretKeySpec(byteKey, HMAC_SHA512); sha512_HMAC.init(keySpec); byte[] mac_data = sha512_HMAC. doFinal(message.getBytes("UTF-8")); return Base64.getUrlEncoder().encodeToString(mac_data); } public DigestCreator(String userId, String clientId, String clientSecret, String clinic_id, String transactionId, String payload) { this.userId = userId; this.clientId = clientId; this.clientSecret = clientSecret; this.clinicId = clinic_id; this.transactionId = transactionId; this.payload = payload; } public static void main( String args[] ) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException { if ((args.length < 5) || (args.length > 6)) { System.out.println("usage: java -cp . org.baudekin.DigestCreator user_id clinic_id client_id, client_secret, transaction_id, <pay load>"); System.exit(-1); } String payload = null; // We are doing post if (args.length == 6) { payload = args[5]; } DigestCreator dc = new DigestCreator(args[0], args[2], args[3], args[1], args[4], payload); System.out.println("Token: " + dc.createToken()); System.exit(0); } }
The post as md5 digest bug check. I will correct later.
Generate GET Request Digest
java -cp ./lib com.sunwave.DigestCreatorsunwave-admin1 131 vQl91X514m11dTHHGYQPQkxJqNPxgbdJ C0iGincSREijXqeuB3P9sDdj1ZU6UwqVaUc6VLwhpcx2sBQmB85k8zWuIKSc6gkCAcnXm4JTk2YBFpH5fFDEPH0JyKg4SgchallGmNDc9fNkO1ojZxyKaZ5murQZFDvSW7iJl1CM5JESube8P0cdlqtiLoHb7BP4293S6FqG557TbIPS61ACp0lfAOu9fNXD6L2LD24j7QMRZpM8GE6GQOnY5nTaHGn42eBMjB8iMS9gx4P7iStJirC0vjq2miSC 0000002
Generate POST Request Digest
Data Dictionary
Term | Definition |
---|---|
Client Id | The unique identifier we assign to user of external API call. Random 32 character string generated using custom algorithm. https://github.com/sunwavehealth/SunwaveEMR/blob/d46b653451ce24623a0c6c72d0aa4e2313c5c0f9/src/main/java/com/sunwave/emr/server/security/ExternalApplicationProcessor.java#L96 |
Client Secret | Secret we assign to the user of the Rest API. It is 256 characters randomly generated by custom algorithm. https://github.com/sunwavehealth/SunwaveEMR/blob/d46b653451ce24623a0c6c72d0aa4e2313c5c0f9/src/main/java/com/sunwave/emr/server/security/ExternalApplicationProcessor.java#L96 |
User Id | The use’s account for Sunwave Health. I must have email to be used for this validation. |
Clinic Id | Realm to the user id is to be validated against. |
Transaction Id | A unique id the customer generates for each transaction. Can not be reused. |
Payload | Only used for POST operations and is the base 64 encoded string representing the data to be put into Sunwave. |
MD5 Digest | Message-digest algorithm for producing 128-bit hash values. See https://en.wikipedia.org/wiki/MD5 Sunwave uses it as a checksum to validate the request has not been modified. |
Seed | For GET requests: |
HMAC | Hash Based Method Authentication code see https://en.wikipedia.org/wiki/HMAC Used to verify both the data integrity and authenticity of the user’s request. |
Token | This is the string to used as the Digest. |
Software Design
GET Request Validation Trace
API Security Filter Digest Validation PATH https://github.com/sunwavehealth/SunwaveEMR/blob/51252a9bb7a7d193a9cb929a7c22b04c2ad7fcf5/src/main/java/com/sunwave/emr/server/security/APISecurityFilter.java#L44
Validate GET if any of the validations fail “Invalid Request“ is returned. https://github.com/sunwavehealth/SunwaveEMR/blob/d46b653451ce24623a0c6c72d0aa4e2313c5c0f9/src/main/java/com/sunwave/emr/server/security/DigestValidator.java#L41
Validate Date and Transaction Id https://github.com/sunwavehealth/SunwaveEMR/blob/d46b653451ce24623a0c6c72d0aa4e2313c5c0f9/src/main/java/com/sunwave/emr/server/security/DigestValidator.java#L49
Validate Date - The request timestamp must be with in 300000 millisecond (5 minute) time window before now. https://github.com/sunwavehealth/SunwaveEMR/blob/d46b653451ce24623a0c6c72d0aa4e2313c5c0f9/src/main/java/com/sunwave/emr/server/security/DigestValidator.java#L91
Validate Transaction Id - Transaction Id can not be reused this checks for that in the
sw_api_transaction
table using transaction_id and clinic_id https://github.com/sunwavehealth/SunwaveEMR/blob/d46b653451ce24623a0c6c72d0aa4e2313c5c0f9/src/main/java/com/sunwave/emr/server/security/DigestValidator.java#L79select id from sw_api_transaction where transaction_id = ? and clinic_id = ?",
If the transaction id is not found the validation passes and the transaction id is inserted into
sw_api_transaction
table.insert into sw_api_transaction (transaction_id, created_on,clinic_id) values (?,str_to_date(?,'%Y-%m-%d %H:%i:%s'),?)"
Validate The user exists and has an email address from user_email table. https://github.com/sunwavehealth/SunwaveEMR/blob/d46b653451ce24623a0c6c72d0aa4e2313c5c0f9/src/main/java/com/sunwave/emr/server/security/DigestValidator.java#L58
select user_email from sw_user_clinic where sw_user_clinic.clinic_id = ? and sw_user_clinic.user_email = ?
Validate HMAC if the passed in HMAC does not match the calculated HMAC the validation fails.
Get Private Key from
sw_external_application
table: https://github.com/sunwavehealth/SunwaveEMR/blob/d46b653451ce24623a0c6c72d0aa4e2313c5c0f9/src/main/java/com/sunwave/emr/server/security/DigestValidator.java#L72select client_secret from sw_external_application where client_id = ? and clinic_id = ?
Calculate the HMAC using the seed and private key https://github.com/sunwavehealth/SunwaveEMR/blob/d46b653451ce24623a0c6c72d0aa4e2313c5c0f9/src/main/java/com/sunwave/emr/server/security/DigestValidator.java#L62 and https://github.com/sunwavehealth/SunwaveEMR/blob/d46b653451ce24623a0c6c72d0aa4e2313c5c0f9/src/main/java/com/sunwave/emr/server/util/JWT.java#L97
String parts[] = token.split(":"); String userId = parts[0]; String clientId = parts[1]; String dateTime = parts[2]; String clinicId = parts[3]; String transactionId = parts[4]; String hmac = parts[5]; String seed = userId + ":" + clientId + ":" + dateTime + ":" + clinicId + ":" + transactionId;
##### Begin GET Validation Trace 01 Feb 2023 10:21:01,666~ DEBUG ~ com.sunwave.emr.server.util.LoggerUtils:37 ~ ~ *********** com.sunwave.emr.server.security.DigestValidator::validateTransactionId line: 79 01 Feb 2023 10:21:01,671~ DEBUG ~ com.sunwave.emr.server.util.LoggerUtils:38 ~ ~ select id from sw_api_transaction where transaction_id = '0000002' and clinic_id = '131' 01 Feb 2023 10:21:08,263~ DEBUG ~ com.sunwave.emr.server.util.LoggerUtils:37 ~ ~ *********** com.sunwave.emr.server.security.DigestValidator::validateTransactionId line: 84 01 Feb 2023 10:21:08,265~ DEBUG ~ com.sunwave.emr.server.util.LoggerUtils:38 ~ ~ insert into sw_api_transaction (transaction_id, created_on,clinic_id) values ('0000002',str_to_date('2023-02-01 10:21:06','%Y-%m-%d %H:%i:%s'),'131') 01 Feb 2023 10:22:09,078~ DEBUG ~ com.sunwave.emr.server.util.LoggerUtils:37 ~ ~ *********** com.sunwave.emr.server.security.DigestValidator::validateGET line: 52 01 Feb 2023 10:22:09,081~ DEBUG ~ com.sunwave.emr.server.util.LoggerUtils:38 ~ ~ select user_email from sw_user_clinic where sw_user_clinic.clinic_id = '131' and sw_user_clinic.user_email = 'sunwave-admin1' 01 Feb 2023 10:22:12,955~ DEBUG ~ com.sunwave.emr.server.util.LoggerUtils:37 ~ ~ *********** com.sunwave.emr.server.security.DigestValidator::getPrivateKey line: 73 01 Feb 2023 10:22:12,959~ DEBUG ~ com.sunwave.emr.server.util.LoggerUtils:38 ~ ~ select client_secret from sw_external_application where client_id = 'vQl91X514m11dTHHGYQPQkxJqNPxgbdJ' and clinic_id = '131' ##### End GET Validation Trace ##### Begin GET Users Trace 01 Feb 2023 10:23:55,725~ DEBUG ~ com.sunwave.emr.server.util.LoggerUtils:37 ~ ~ *********** com.sunwave.emr.server.security.APISecurityFilter::validateAPICalls line: 198 01 Feb 2023 10:23:55,726~ DEBUG ~ com.sunwave.emr.server.util.LoggerUtils:38 ~ ~ select api_call_limit_per_day from sw_clinic where clinic_id = '131' 01 Feb 2023 10:23:55,739~ DEBUG ~ com.sunwave.emr.server.util.LoggerUtils:37 ~ ~ *********** com.sunwave.emr.server.security.APISecurityFilter::validateAPICalls line: 200 01 Feb 2023 10:23:55,739~ DEBUG ~ com.sunwave.emr.server.util.LoggerUtils:38 ~ ~ select call_log_count from sw_api_call_count_log where call_log_date = '2023-02-01' and clinic_id = '131' 01 Feb 2023 10:23:55,747~ DEBUG ~ com.sunwave.emr.server.util.LoggerUtils:37 ~ ~ *********** com.sunwave.emr.server.security.APISecurityFilter::validateAPICalls line: 207 01 Feb 2023 10:23:55,747~ DEBUG ~ com.sunwave.emr.server.util.LoggerUtils:38 ~ ~ update sw_api_call_count_log set call_log_count = call_log_count + 1 where call_log_date = '2023-02-01' and clinic_id = '131' 01 Feb 2023 10:23:55,757~ DEBUG ~ com.sunwave.emr.server.util.LoggerUtils:37 ~ ~ *********** com.sunwave.emr.server.Processor::getParentClinicId line: 1535 01 Feb 2023 10:23:55,757~ DEBUG ~ com.sunwave.emr.server.util.LoggerUtils:38 ~ ~ SELECT parent_clinic_id FROM sw_clinic where clinic_id='131' 01 Feb 2023 10:23:55,762~ DEBUG ~ com.sunwave.emr.server.util.LoggerUtils:37 ~ ~ *********** com.sunwave.emr.server.Processor::getTimezoneId line: 1584 01 Feb 2023 10:23:55,762~ DEBUG ~ com.sunwave.emr.server.util.LoggerUtils:38 ~ ~ SELECT timezone_id, clinic_id FROM sw_clinic where id='131' 01 Feb 2023 10:23:55,768~ DEBUG ~ com.sunwave.emr.server.util.LoggerUtils:37 ~ ~ *********** com.sunwave.emr.dao.UserDao::getUsersByClinicExcludingSunwaveUsers line: 763 01 Feb 2023 10:23:55,768~ DEBUG ~ com.sunwave.emr.server.util.LoggerUtils:38 ~ ~ SELECT mb2_user.id, mb2_user.email, mb2_user.first_name, mb2_user.last_name, ifnull(mb2_user.created_on,sw_user_clinic.created_on) created_on, mb2_user.created_by, if(sw_user_clinic.last_login='2011-01-01 00:00:00', '', sw_user_clinic.last_login) last_login FROM mb2_user inner join sw_user_clinic on sw_user_clinic.user_email = mb2_user.email WHERE sw_user_clinic.clinic_id='131' and ((mb2_user.is_sunwave != 'true' and mb2_user.is_sunwave_user != 'true') or (mb2_user.is_sunwave is null and mb2_user.is_sunwave_user is null)) ##### End GET Users Trace