ADFS Custom Claim Rule email To LowerCase SAML Response - saml

I am trying to create a custom claim rule in adfs to re-write the email address to NameId but in lowercase.
The reason is Responsys reads the claims and is case sensitive when comparing the NameId in the SAML Response it fails. Our Active Directory has email address as mixed case for some users and lowercase for others. e.g. LJeary# or ljeary#
I have added a custom attribute store to do the lowercase part but I am not seeing the Claim in the SAMLResponse.
as per http://macintheoffice.com/?q=node/5
I need help to create a custom claim rule which will successfully send the email address as lowercase in the outgoing claim NameID
Should see this
<Subject>
<NameID>ljeary#abc.com</NameID>
<SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<SubjectConfirmationData NotOnOrAfter="2017-05-10T03:24:20.358Z" Recipient="https://interact2.responsys.net/authentication/login/loginSSO" />
</SubjectConfirmation>
</Subject>
but see this
<Subject>
<SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<SubjectConfirmationData NotOnOrAfter="2017-05-10T03:24:20.358Z" Recipient="https://interact2.responsys.net/authentication/login/loginSSO" />
</SubjectConfirmation>
</Subject>
missing the NameID attribute.
Custom Claim Rule used is
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]
=> issue(store = "StringProcessing", types = ("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"), query = "toLower", param = c.Value);
answer:
Thanks #nzpcmad . Came up with a similar process by returning the email address and storing in an incoming claim. Then creating another rule to take the email incoming claim transform to lower and assign to outgoing claim NameID.
ADFS Claim Rules

Are you sure that something is being returned from the custom attribute store?
The error could be because of a missing NameID format.
Try returning a dummy claim e.g. http://company/temp which contains the lower case email and then use a Transform rule to transform the dummy claim to NameID.
You could also code this yourself.
Or if you like typing :-)

As #nzpcmad suggests, you can do this by issuing a temporary claim, then using RegExReplace to convert it to lowercase. While inelegant, it does result in a solution that requires no installations on the AD FS server.
Example with UPN going to a custom claim as lowercase:
Rule 1:
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer == "AD AUTHORITY"]
=> add(store = "Active Directory", types = ("urn:temp_upn"), query = ";userPrincipalName;{0}", param = c.Value);
Rule 2:
c:[Type == "urn:temp_upn"]
=> issue(Type = "https://aws.amazon.com/SAML/Attributes/RoleSessionName", Value = RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(RegExReplace(c.Value, "A", "a"), "B", "b"), "C", "c"), "D", "d"), "E", "e"), "F", "f"), "G", "g"), "H", "h"), "I", "i"), "J", "j"), "K", "k"), "L", "l"), "M", "m"), "N", "n"), "O", "o"), "P", "p"), "Q", "q"), "R", "r"), "S", "s"), "T", "t"), "U", "u"), "V", "v"), "W", "w"), "X", "x"), "Y", "y"), "Z", "z"));

Related

AWS Boto3 SES forwarding email with attachment - Special Character in 'From' header

When forwarding an email through AWS SES using boto3, I am failing either to keep the attachment, or sending the email altogether if the 'From' header contains a special character.
I have tried this using SES resource's two functions, send_email and send_raw_email.
I can send emails just fine with the send_email function, except I could not find a way to keep any attachments.
I can send emails with attachments that have regular 'From' headers with send_raw_email, but if there is a special character in the 'From' header, I receive the below error:
When From is encoded with UTF-8:
An error occurred (InvalidParameterValue) when calling the SendRawEmail operation: Missing final '#domain'"
When From is encoded with ascii:
An error occurred (InvalidParameterValue) when calling the SendRawEmail operation: Local address contains control or whitespace
I have read through many SO posts, where examples with attachments always use the send_raw_email function. This makes sense to me as the send_email function only accepts a Body field from what I can tell, whereas send_raw_email accepts the email object in byte form. However, they seem to ignore the possibility of special characters in the 'From' header, or I am doing something wrong.
How can I avoid the above errors and preferably retain the original From header while also retaining any attachments?
Below is some simplified code that I use:
s3 = boto3.client("s3")
data = s3.get_object(Bucket=EMAIL_BUCKET, Key=f"{INCM_PREFIX_RAW}/{message_id}")
contents = data["Body"].read()
message = email.message_from_string(contents.decode("utf-8"))
characters = {"Ğ": "G", "Ü": "U", "Ş": "S", "İ": "I", "Ö": "O", "Ç": "C",
"ğ": "g", "ü": "u", "ş": "s", "ı": "i", "ö": "o", "ç": "c", '"': "", " ": ""}
for character in characters: message_from = message_from.replace(character, characters[character])
if "<" in message_from:
sender = message_from.split("<")[0]
if "#" in sender: sender = "<example#example.com>"
else: sender = sender + "<example#example.com>"
else: sender = "<example#example.com>"
message["From"] = sender
ses_client = boto3.client("ses")
response = ses_client.send_raw_email(
Destinations=[destination],
RawMessage={"Data": message.as_bytes()}
)
Here is the alternative approach of how I use the send_email function:
response = ses_client.send_email(
Source=message["From"], Destination={"ToAddresses": [destination]},
Message={"Subject": {"Data": message["Subject"]},
"Body": {"Html": {"Data": message_html, "Charset": "utf-8"}}},
ReplyToAddresses=[message_from], ReturnPath=message["From"]
)
Notes: I am using From as an address from my own domain, preceded by the original senders name, such as '"John Doe" <my#domain.com>'

Make JSON Parsing & Error Handling More Functional in Scala

I have the following piece of code that I use to read the incoming JSON which looks like this:
{
"messageTypeId": 2,
"messageId": "19223201",
"BootNotification" :
{
"reason": "PowerUp",
"chargingStation": {
"serialNumber" : "12345",
"model" : "",
"vendorName" : "",
"firmwareVersion" : "",
"modem": {
"iccid": "",
"imsi": ""
}
}
}
}
I have the following reads using play-json that would process this JSON:
implicit val ocppCallRequestReads: Reads[OCPPCallRequest] = Reads { jsValue =>
val messageTypeId = (jsValue \ 0).toOption
val messageId = (jsValue \ 1).toOption
val actionName = (jsValue \ 2).toOption
val payload = (jsValue \ 3).toOption
(messageTypeId.zip(messageId.zip(actionName.zip(payload)))) match {
case Some(_) => JsSuccess(
OCPPCallRequest( // Here I know all 4 exists, so safe to call head
messageTypeId.head.as[Int],
messageId.head.as[String],
actionName.head.as[String],
payload.head
)
)
case None => JsError( // Here, I know that I have to send a CallError back!
"ERROR OCCURRED" // TODO: Work on this!
)
}
}
It is not playing nicely when it comes to delivering the exact error message for the case None. It is all in or nothing, but what I want to avoid is that in my case None block, I would like to avoid looking into each of the Option and populate the corresponding error message. Any ideas on how I could make it more functional?

How do you post form data using pytest?

I'm trying to write a unit test that posts form data. The actual line in question is:
def test_create_request():
with app.test_client() as test_client:
app_url = '/requests/'
with app.app_context():
new_request = get_new_request()
form_data = json.dumps(new_request, default=str)
print('FORM DATA: ', form_data)
resp = test_client.post(app_url, data=form_data, headers={'Content-Type': 'application/json'})
assert resp.status_code == 200
s = json.loads(resp.data)
assert s['success'] == True
Where new_request is a dict representation of an object. The print statement yields (I've formatted it a bit):
FORM DATA: {
"request_id": "6",
"state_id": 1,
"orig_project_id": "1",
"orig_project_code": "QQQ",
"orig_allocated_funding": "123.45",
"orig_ytd_spend": "123.45",
"orig_ytd_commit": "123.45",
"orig_ytd_ocnr": "123.45",
"new_project_id": 2,
"new_funding_amount": 123.45,
"new_ytd_spend": 123.45,
"new_ytd_commit": 123.45,
"new_ytd_ocnr": 123.45,
"plan": "this is the plan",
"reason": "this is the reason",
"sm_director": "sm.dir#example.com",
"submitted_by": "xfgbn#vexample.com",
"created_on": "2021-09-14 16:32:55",
"meets_approval_guidelines": null
}
In the flask form, most fields are required. When I try to post the data, the form.validate_on_submit() function in the view's route returns False, and the 2nd assert fails. WTForms claims that none of the required orig_allocated_funding, orig_ytd_spend, orig_ytd_commit, orig_ytd_ocnr, new_project_id, new_funding_amount, new_ytd_spend, new_ytd_commit, new_ytd_ocnr, reason, plan, sm_director, or submitted_by fields are supplied.
I've read several tutorials, and I can't see what I'm doing wrong. Can anyone help?
What I was able to make work was scraping form_data altogether:
def test_create_request():
with app.test_client() as test_client:
app_url = '/requests/'
with app.app_context():
new_request = get_new_request()
resp = test_client.post(app_url, data=new_request)
assert resp.status_code == 200
s = json.loads(resp.data)
print(s['html'])
assert s['success'] == True

jose4j, decrypt JWE with symmetric key

I'm trying to reproduce a decoding of a JWE starting from jwt.io as an example and translating into code by using library jose4j
From site jwt.io I have the following:
HEADER:
{
"alg": "HS256"
}
PAYLOAD:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
VERIFY SIGNATURE:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
Fdh9u8rINxfivbrianbbVT1u232VQBZYKx1HGAGPt2I
)
the secret base64 is not encoded.
Now I try to reproduce the situation with jose4j and then having as a result the same value on the encoded field, which is:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.jOJ7G4oijaDk9Tr4ntAXczd6PlI4oVvBU0_5cf7oaz4
Then:
Key key = new HmacKey("Fdh9u8rINxfivbrianbbVT1u232VQBZYKx1HGAGPt2I".getBytes(StandardCharsets.UTF_8));
JsonWebEncryption jwe = new JsonWebEncryption();
String payload = Json.createObjectBuilder()
.add("sub", "1234567890")
.add("name", "John Doe")
.add("iat", "1516239022")
.build()
.toString();
jwe.setPayload(payload);
jwe.setHeader("alg", "HS256");
jwe.setKey(key);
String serializedJwe = jwe.getCompactSerialization();
System.out.println("Serialized Encrypted JWE: " + serializedJwe);
However I get this error:
org.jose4j.lang.InvalidAlgorithmException: HS256 is an unknown, unsupported or unavailable alg algorithm (not one of [RSA1_5, RSA-OAEP, RSA-OAEP-256, dir, A128KW, A192KW, A256KW, ECDH-ES, ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW, PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW, A128GCMKW, A192GCMKW, A256GCMKW]).
HS256 is a JWS algorithm so you'd need to use JsonWebSignature rather than JsonWebEncryption to accomplish what it looks like you're trying to do.

constructing request data for SOAP endpoint without WSDL

The endpoint URL looks like this (not an actual url)
https://webservices.abcde.com/ThirdParty/PostData.V55.ashx/ProcessRequest
It does not have a WSDL, and in the documentation, there's a sample request XML.
It's huge. I am adding the first couple lines from it below.
<MESSAGE xmlns:agentnet="http://services.abcde.com/entity/agentnet/v2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.mismo.org/residential/2009/schemas/v32">
<ABOUT_VERSIONS>
<ABOUT_VERSION>
<AboutVersionIdentifier>ClientSystem</AboutVersionIdentifier>
<DataVersionIdentifier>1.0</DataVersionIdentifier>
<DataVersionName>ASDFSFD</DataVersionName>
</ABOUT_VERSION>
</ABOUT_VERSIONS>
<DEAL_SETS>
<DEAL_SET>
<DEALS>
<DEAL>
<PARTIES/>
<SERVICES>
<SERVICE>
<SERVICE_PRODUCT>
<SERVICE_PRODUCT_REQUEST>
<EXTENSION>
<OTHER>
<agentnet:AGENTNET_PRODUCT_REQUEST>
<agentnet:AgentNetServiceType>GET_DATA</agentnet:AgentNetServiceType>
<agentnet:AGENTNET_GET_DATA>
<agentnet:GetRequestType>ACCOUNTS</agentnet:GetRequestType>
</agentnet:AGENTNET_GET_DATA>
</agentnet:AGENTNET_PRODUCT_REQUEST>
...
...
...
...
... (the XML is huge)
With other endpoints, I was able to use a SOAP library like savon to generate the XML payload using a small Ruby Hash(dictionary).
I assume that was possible because those were WSDLs?
Would it be possible to generate the payload the same way by passing only some essential data (for example, GET_DATA and ACCOUNTS in the example), or should I manually construct the XML payload strings manually (maybe using some XML library)?
I really want to avoid manually constructing XML payloads since the code will not be readable and will be hard to work with in general. Is there a way to avoid it?
You can definitively create a Savon client without using the WSDL. I personally like this better because I believe it's more performant.
You have to define endpoint and namespacewhen you create your client, like this fictitious example:
require 'savon'
c = Savon.client(endpoint: "http://www.example.com",
namespace: "urn:ns.example.com",
log: true,
log_level: :debug,
pretty_print_xml: true)
r = c.call(:call,
:message => {
:InquiryParam => [
{"crmParam" => 123,
:attributes! => { "crmParam" => { "name" => "AccountNumber" }}},
{"crmParam" => 456,
:attributes! => { "crmParam" => { "name" => "history" }}}
]
}
)