I’m a little stuck because authentication was already performed for my account when testing from postman and while setting up the code. For some reason it finds the authentication, I think from the cookies for the sendHttpGetRequest. In postman I know how to delete the cookies (and I can restart the whole authentication), but I cannot find a way to do that in OpenHAB…
Here’s my current progress:
rule "VOC Start OTP"
when
Item VOC_Start_OTP changed to ON
then
// Variable to incicate whether authentication is completed
var completed = false
// Step 1: Initial GET request for authorization
var authURL = "https://volvoid.eu.volvocars.com/as/authorization.oauth2?client_id=h4Yf0b&response_type=code&response_mode=pi.flow" // no acr_values or scope required for initial request
var authHeaders = newHashMap() // no headers required for initial request
var authResponse = sendHttpGetRequest(authURL, authHeaders, 10000)
logInfo("VOC", "Authorization response: " + authResponse)
// Auth already completed
//
// {
// "id": "Do3CWHgNZE",
// "pluginTypeId": "7RmQNDWaOnBoudTufx2sEw",
// "status": "COMPLETED",
// "authorizeResponse": {
// "code": "kVCqC3kk8GQicmnN2RP3EQBuDKaFbH3yqpVgwv0v"
// },
// "user": {
// "id": "83431904-5b4b-42af-9c95-e7deeb840f75",
// "username": "*****obfuscated*****"
// },
// "_links": {
// "self": {
// "href": "https://volvoid.eu.volvocars.com/pf-ws/authn/flows/Do3CWHgNZE"
// }
// }
// }
// Auth Username Password required
//
// {
// "id": "yYI6qv5k0A",
// "pluginTypeId": "7RmQNDWaOnBoudTufx2sEw",
// "status": "USERNAME_PASSWORD_REQUIRED",
// "showRememberMyUsername": false,
// "showThisIsMyDevice": false,
// "thisIsMyDeviceSelected": false,
// "showCaptcha": false,
// "rememberMyUsernameSelected": false,
// "_links": {
// "self": {
// "href": "https://volvoid.eu.volvocars.com/pf-ws/authn/flows/yYI6qv5k0A"
// },
// "checkUsernamePassword": {
// "href": "https://volvoid.eu.volvocars.com/pf-ws/authn/flows/yYI6qv5k0A"
// }
// }
// }
var authStatus = transform("JSONPATH", "$.status", authResponse)
if (authStatus == "USERNAME_PASSWORD_REQUIRED") {
logInfo("VOC", "Authorization succes, needs username & password")
val username = volvousername.state.toString()
val password = volvopassword.state.toString()
// Step 2: POST username and password
val checkUsernameUrl = transform("JSONPATH", "$._links.checkUsernamePassword.href", authResponse) + "?action=checkUsernamePassword"
logInfo("VOC", "Authorization post URL: " + checkUsernameUrl)
var checkUsernameContentType = "application/json"
val checkUsernameContent = '{"username": "' + username + '", "password": "' + password + '"}'
val checkUsernameHeaders = newHashMap(
"x-xsrf-header" -> "PingFederate"
)
val authPostResponse = sendHttpPostRequest(checkUsernameUrl, checkUsernameContentType, checkUsernameContent, checkUsernameHeaders, 10000)
logInfo("VOC", "Authorization post response: " + authPostResponse)
// Auth already completed
//
// {
// "id": "Dfs09s89o4",
// "pluginTypeId": "7RmQNDWaOnBoudTufx2sEw",
// "status": "COMPLETED",
// "authorizeResponse": {
// "code": "1jnA1QklBCTDTGvWufj1uKn39Gwj-QstrqgqxEKB"
// },
// "user": {
// "id": "83431904-5b4b-42af-9c95-e7deeb840f75",
// "username": "*****obfuscated*****"
// },
// "_links": {
// "self": {
// "href": "https://volvoid.eu.volvocars.com/pf-ws/authn/flows/Dfs09s89o4"
// }
// }
// }
// OTP required
//
// {
// "id": "Zrre4SipNk",
// "pluginTypeId": "7r5wkzvoQS8iEJEpdMYqmA",
// "status": "OTP_REQUIRED",
// "devices": [
// {
// "id": "f9262bc1-27a0-3e1a-bc06-49835a679205",
// "type": "EMAIL",
// "target": "*****obfuscated*****"
// }
// ],
// "user": {
// "username": "*****obfuscated*****"
// },
// "selectedDeviceRef": {
// "id": "f9262bc1-27a0-3e1a-bc06-49835a679205"
// },
// "_links": {
// "cancelAuthentication": {
// "href": "https://volvoid.eu.volvocars.com/pf-ws/authn/flows/Zrre4SipNk"
// },
// "resendOtp": {
// "href": "https://volvoid.eu.volvocars.com/pf-ws/authn/flows/Zrre4SipNk"
// },
// "selectDevice": {
// "href": "https://volvoid.eu.volvocars.com/pf-ws/authn/flows/Zrre4SipNk"
// },
// "self": {
// "href": "https://volvoid.eu.volvocars.com/pf-ws/authn/flows/Zrre4SipNk"
// },
// "checkOtp": {
// "href": "https://volvoid.eu.volvocars.com/pf-ws/authn/flows/Zrre4SipNk"
// }
// }
// }
var authPostResponseStatus = transform("JSONPATH", "$.status", authPostResponse)
if (authPostResponseStatus == "OTP_REQUIRED") {
logInfo("VOC", "Authorization succes, needs OTP")
// Step 3: OTP input
val otpDevice = transform("JSONPATH", "$.devices[0].type", authPostResponse)
val otpTarget = transform("JSONPATH", "$.devices[0].target", authPostResponse)
volvootpresponse.postUpdate("OTP sent to " + otpDevice + " (" + otpTarget + ")")
logInfo("VOC", "OTP sent to " + otpDevice + " (" + otpTarget + ")")
// Toggle VOC_Start_OTP item to show the rule finished succesfully
VOC_Start_OTP.postUpdate(OFF)
} else if (authPostResponseStatus == "COMPLETED") {
var completedUserName = transform("JSONPATH", "$.user.username", authPostResponse)
val authCode = transform("JSONPATH", "$.authorizeResponse.code", authPostResponse)
completed = true
} else {
logError("VOC", "Username/password authentication failed")
}
} else if (authStatus == "COMPLETED") {
var completedUserName = transform("JSONPATH", "$.user.username", authResponse)
val authCode = transform("JSONPATH", "$.authorizeResponse.code", authResponse)
completed = true
} else {
logError("VOC", "Initial authorization failed")
volvootpresponse.postUpdate("Initial authorization failed")
}
if (completed) {
logInfo("VOC", "Authorization was already (for " + completedUserName + ") completed, re-setting tokens")
logInfo("VOC", "Authorization code: " + authCode)
val tokenUrl = "https://volvoid.eu.volvocars.com/as/token.oauth2"
var tokenContentType = "application/x-www-form-urlencoded"
val tokenContent = "grant_type=authorization_code&code=" + authCode
val tokenHeaders = newHashMap(
'Authorization' -> 'Basic aDRZZjBiOlU4WWtTYlZsNnh3c2c1WVFxWmZyZ1ZtSWFEcGhPc3kxUENhVXNpY1F0bzNUUjVrd2FKc2U0QVpkZ2ZJZmNMeXc=',
'User-Agent' -> 'okhttp/4.10.0',
'x-xsrf-header' -> 'PingFederate',
'Accept-Encoding' -> 'gzip'
)
val tokenResponse = sendHttpPostRequest(tokenUrl, tokenContentType, tokenContent, tokenHeaders, 10000)
logInfo("VOC", "Token response: " + tokenResponse)
if (tokenResponse !== null) {
// Store token in a .ini format or OpenHAB item
val accessToken = transform("JSONPATH", "$.access_token", tokenResponse)
val refreshToken = transform("JSONPATH", "$.refresh_token", tokenResponse)
val expiresIn = transform("JSONPATH", "$.expires_in", tokenResponse)
logInfo("VOC", "Token retrieved: " + accessToken)
logInfo("VOC", "Refresh token: " + refreshToken)
logInfo("VOC", "Expires in: " + expiresIn)
volvootpresponse.postUpdate("Token retrieved!")
// Optionally write to OpenHAB items or a file
// Use persistence or scripting to store the token
} else {
logError("VOC", "Error fetching token")
volvootpresponse.postUpdate("Error fetching token")
}
// Toggle VOC_Start_OTP item to show the rule finished succesfully
VOC_Start_OTP.postUpdate(OFF)
}
end
rule "VOC Verify OTP"
when
Item VOC_Verify_OTP changed to ON or
Item volvootp received update
then
// Headers for the requests
val headers = newHashMap(
"authorization" -> "Basic aDRZZjBiOlU4WWtTYlZsNnh3c2c1WVFxWmZyZ1ZtSWFEcGhPc3kxUENhVXNpY1F0bzNUUjVrd2FKc2U0QVpkZ2ZJZmNMeXc=",
"user-agent" -> "okhttp/4.10.0",
"Accept-Encoding" -> "gzip",
"Content-Type" -> "application/json; charset=utf-8"
)
// Assuming OTP is input manually in OpenHAB UI through an item
val otp = volvootp.state.toString // Replace with appropriate method for user input
val checkOtpUrl = transform("JSONPATH", "$._links.checkOtp.href", authPostResponse) + "?action=checkOtp"
val otpBody = '{"otp": "' + otp + '"}'
val otpPostResponse = sendHttpPostRequest(checkOtpUrl, otpBody, "application/json", headers, 10000)
// {
// "id": "IBROnO7ZBU",
// "pluginTypeId": "7r5wkzvoQS8iEJEpdMYqmA",
// "status": "OTP_VERIFIED",
// "_links": {
// "self": {
// "href": "https://volvoid.eu.volvocars.com/pf-ws/authn/flows/IBROnO7ZBU"
// },
// "continueAuthentication": {
// "href": "https://volvoid.eu.volvocars.com/pf-ws/authn/flows/IBROnO7ZBU"
// }
// }
// }
if (otpPostResponse !== null && otpPostResponse.contains("OTP_VERIFIED")) {
// Step 4: Continue authentication
val continueUrl = transform("JSONPATH", "$._links.continueAuthentication.href", otpPostResponse) + "?action=continueAuthentication"
val continueResponse = sendHttpGetRequest(continueUrl, headers, 10000)
// {
// "id": "IBROnO7ZBU",
// "pluginTypeId": "7r5wkzvoQS8iEJEpdMYqmA",
// "status": "COMPLETED",
// "authorizeResponse": {
// "code": "yUGn39NYMX9SQvhYs0aP_2V4GsWlBtEZF71gwv0v"
// },
// "user": {
// "id": "83431904-5b4b-42af-9c95-e7deeb840f75",
// "username": "*****obfuscated*****"
// },
// "_links": {
// "self": {
// "href": "https://volvoid.eu.volvocars.com/pf-ws/authn/flows/IBROnO7ZBU"
// }
// }
// }
if (continueResponse !== null) {
// Step 5: Get token
val authCode = transform("JSONPATH", "$.authorizeResponse.code", continueResponse)
logInfo("VOC", "Authorization code: " + authCode)
val tokenUrl = "https://volvoid.eu.volvocars.com/as/token.oauth2"
var tokenContentType = "application/x-www-form-urlencoded"
val tokenContent = "grant_type=authorization_code&code=" + authCode
val tokenHeaders = newHashMap(
'Authorization' -> 'Basic aDRZZjBiOlU4WWtTYlZsNnh3c2c1WVFxWmZyZ1ZtSWFEcGhPc3kxUENhVXNpY1F0bzNUUjVrd2FKc2U0QVpkZ2ZJZmNMeXc=',
'User-Agent' -> 'okhttp/4.10.0',
'x-xsrf-header' -> 'PingFederate',
'Accept-Encoding' -> 'gzip'
)
val tokenResponse = sendHttpPostRequest(tokenUrl, tokenContentType, tokenContent, tokenHeaders, 10000)
logInfo("VOC", "Token response: " + tokenResponse)
if (tokenResponse !== null) {
// Store token in a .ini format or OpenHAB item
val accessToken = transform("JSONPATH", "$.access_token", tokenResponse)
val refreshToken = transform("JSONPATH", "$.refresh_token", tokenResponse)
val expiresIn = transform("JSONPATH", "$.expires_in", tokenResponse)
logInfo("VOC", "Token retrieved: " + accessToken)
logInfo("VOC", "Refresh token: " + refreshToken)
logInfo("VOC", "Expires in: " + expiresIn)
volvootpresponse.postUpdate("Token retrieved!")
// Optionally write to OpenHAB items or a file
// Use persistence or scripting to store the token
} else {
logError("VOC", "Error fetching token")
volvootpresponse.postUpdate("Error fetching token")
}
// Toggle VOC_Start_OTP item to show the rule finished succesfully
VOC_Verify_OTP.postUpdate(OFF)
} else {
logError("VOC", "Error in continue authentication")
}
} else {
logError("VOC", "OTP verification failed")
}
end