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.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.util.Base64; import java.util.TimeZone; import java.util.Date; import org.apache.commons.codec.digest.DigestUtils; 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 { return Base64.getEncoder().encodeToString(DigestUtils.md5Hex(payload.getBytes()).getBytes()); } 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.out.println("md5 digest: " + dc.createMd5Digest()); System.exit(0); } }
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>DigestCreater</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <dependencies> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.15</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.4.2</version> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> <configuration> <archive> <manifest> <mainClass> com.sunwave.DigestCreator </mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>8</source> <target>8</target> </configuration> </plugin> </plugins> </build> </project>
Generate GET Request Digest
java -cp ./target/DigestCreater-1.0-SNAPSHOT.jar com.sunwave.DigestCreator sunwave-admin1 131 vQl91X514m11dTHHGYQPQkxJqNPxgbdJ C0iGincSREijXqeuB3P9sDdj1ZU6UwqVaUc6VLwhpcx2sBQmB85k8zWuIKSc6gkCAcnXm4JTk2YBFpH5fFDEPH0JyKg4SgchallGmNDc9fNkO1ojZxyKaZ5murQZFDvSW7iJl1CM5JESube8P0cdlqtiLoHb7BP4293S6FqG557TbIPS61ACp0lfAOu9fNXD6L2LD24j7QMRZpM8GE6GQOnY5nTaHGn42eBMjB8iMS9gx4P7iStJirC0vjq2miSC 0000002
Generate POST Request Digest
#!/usr/bin/env bash TRANSACTION_ID=$1 #PAYLOAD="eyJjYWxsZXJfZmlyc3RfbmFtZSIgOiAiQXBpLTEwIiwgImNhbGxlcl9sYXN0X25hbWUiIDogIkFwaS0xMCIsICJwYXRpZW50X2ZpcnN0X25hbWUiIDogIkFwaS0xMCIsICJwYXRpZW50X2xhc3RfbmFtZSIgOiAiQXBpLTEwIiwgInBhdGllbnRfZGF0ZV9vZl9iaXJ0aCIgOiAiMTk4MC0wNC0wNCIsICJhY2NvdW50X2lkIjogIjQzMSIsICJzY2hlZHVsZWRfc2VydmljZV9mYWNpbGl0eV9pZCIgOiAiMTIzNCIsICJzY2hlZHVsZWRfYWRtaXNzaW9uX2RhdGUiIDogIjIwMjItMTAtMDgiLCAic2NoZWR1bGVkX2FkbWlzc2lvbl90aW1lIjogIjA5OjAwIiwgInNjaGVkdWxlZF9sZXZlbF9vZl9jYXJlX2lkIjogIjEiLCAic2NoZWR1bGVkX3Byb2dyYW0iOiAiMTA2NSIsICJwYXRpZW50X3Bob25lX21vYmlsZSI6ICI3ODY0Mzg4Nzg2IiwgInBhdGllbnRfcGhvbmVfd29yayI6ICI3ODY0Mzg4Nzg2IiwgInBhdGllbnRfcGhvbmVfaG9tZSI6ICI3ODY0Mzg4Nzg2IiwgInBhdGllbnRfc3NuIjogIjQ1NS02Ni02Njc3IiwgInBhdGllbnRfZW1haWwiOiAiYXBpLTZAZ21haWwuY29tIiwgInBhdGllbnRfYWRkcmVzcyI6ICIzMzc1IFcgNzZTdCIsICJwYXRpZW50X2NpdHkiOiAiTWlhbWkiLCAicGF0aWVudF9zdGF0ZSI6ICJGTCIsICJwYXRpZW50X3ppcCI6ICIzMzAxOSIsICJwYXRpZW50X2dlbmRlcl9jb2RlIjogIk0iLCAicmFjZSI6ICJJbmRpYW4iLCAibWFya2V0aW5nX3NvdXJjZV9jb250YWN0X2lkIjogIjM2IiwgImFkbWlzc2lvbl9yZXByZXNlbnRhdGl2ZSI6ICIyNzAxNiIsICJpbnN1cmFuY2VfcHJvdmlkZXIiOiAiOTc0IiwgIm1lbWJlcl9pZCI6ICIxMTIyIiwgImluc3VyYW5jZV9ncm91cF9udW1iZXIiOiAiMzAwMCIsICJzY2hlZHVsZWRfaW50YWtlX21lc3NhZ2UiOiAiTmVlZCB0byBwYXkifQo=" PAYLOAD='{"caller_first_name" : "Api-10", "caller_last_name" : "Api-10", "patient_first_name" : "Api-10", "patient_last_name" : "Api-10", "patient_date_of_birth" : "1980-04-04", "account_id": "431", "scheduled_service_facility_id" : "1234", "scheduled_admission_date" : "2022-10-08", "scheduled_admission_time": "09:00", "scheduled_level_of_care_id": "1", "scheduled_program": "1065", "patient_phone_mobile": "7864388786", "patient_phone_work": "7864388786", "patient_phone_home": "7864388786", "patient_ssn": "455-66-6677", "patient_email": "api-6@gmail.com", "patient_address": "3375 W 76St", "patient_city": "Miami", "patient_state": "FL", "patient_zip": "33019", "patient_gender_code": "M", "race": "Indian", "marketing_source_contact_id": "36", "admission_representative": "27016", "insurance_provider": "974", "member_id": "1122", "insurance_group_number": "3000", "scheduled_intake_message": "Need to pay"}' # { # "caller_first_name" : "Api-10", # "caller_last_name" : "Api-10", # "patient_first_name" : "Api-10", # "patient_last_name" : "Api-10", # "patient_date_of_birth" : "1980-04-04", # "account_id": "431", # "scheduled_service_facility_id" : "1234", # "scheduled_admission_date" : "2022-10-08", # "scheduled_admission_time": "09:00", # "scheduled_level_of_care_id": "1", # "scheduled_program": "1065", # "patient_phone_mobile": "7864388786", # "patient_phone_work": "7864388786", # "patient_phone_home": "7864388786", # "patient_ssn": "455-66-6677", # "patient_email": "api-6@gmail.com", # "patient_address": "3375 W 76St", # "patient_city": "Miami", # "patient_state": "FL", # "patient_zip": "33019", # "patient_gender_code": "M", # "race": "Indian", # "marketing_source_contact_id": "36", # "admission_representative": "27016", # "insurance_provider": "974", # "member_id": "1122", # "insurance_group_number": "3000", # "scheduled_intake_message": "Need to pay" #} java -jar ./target/DigestCreater-1.0-SNAPSHOT-jar-with-dependencies.jar sunwave-admin1 131 vQl91X514m11dTHHGYQPQkxJqNPxgbdJ C0iGincSREijXqeuB3P9sDdj1ZU6UwqVaUc6VLwhpcx2sBQmB85k8zWuIKSc6gkCAcnXm4JTk2YBFpH5fFDEPH0JyKg4SgchallGmNDc9fNkO1ojZxyKaZ5murQZFDvSW7iJl1CM5JESube8P0cdlqtiLoHb7BP4293S6FqG557TbIPS61ACp0lfAOu9fNXD6L2LD24j7QMRZpM8GE6GQOnY5nTaHGn42eBMjB8iMS9gx4P7iStJirC0vjq2miSC "${TRANSACTION_ID}" "${PAYLOAD}"
source ./postgen.sh test00013
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