imulab / go-scim

Building blocks for servers implementing Simple Cloud Identity Management v2
MIT License
146 stars 56 forks source link

Validation fails in case of integer attributes #6

Open alelb opened 7 years ago

alelb commented 7 years ago

Hi @davidiamyou,

I noticed that if I create a resource with some integer attributes, validation fails. This issue seems related to the JavaScript storing mechanism: all stored numbers in JavaScript are floating point, according to IEEE 754.

So when you perform json unmarshal the type of integer attributes will be float64. And when you perform validation with reflection, crossing this misleading type with the one declared in the schema, it always fails.

imulab commented 7 years ago

hmmm... could you paste your payload and the schema here?

alelb commented 7 years ago

@davidiamyou here the PasswordPolicy schema from the password management draft:

{
    "id": "urn:ietf:params:scim:schemas:core:2_0:policy:Password",
    "name": "Password Policy Schema",
    "description": "This extension defines attributes for a password policy.",
    "attributes": [
        {
            "name": "name",
            "type": "string",
            "multiValued": false,
            "description": "A String that is the name of the policy. Typically used for informational purposes (e.g. to display to the user)",
            "required": true,
            "caseExact": false,
            "mutability": "readWrite",
            "returned": "default",
            "uniqueness": "none"
        },
        {
            "name": "description",
            "type": "string",
            "multiValued": false,
            "description": "A String that describes the current policy. Typically used for informational purposes (e.g. to display to a user).",
            "required": false,
            "caseExact": false,
            "mutability": "readWrite",
            "returned": "default",
            "uniqueness": "none"
        },
        {
            "name": "maxLength",
            "type": "integer",
            "multiValued": false,
            "description": "Maximum password length in characters.",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "minLength",
            "type": "integer",
            "multiValued": false,
            "description": "Minimum password length in characters.",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "minAlphas",
            "type": "integer",
            "multiValued": false,
            "description": "Minimum number of alpha chcars.",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "minNumerals",
            "type": "integer",
            "multiValued": false,
            "description": "Minimum number of numeric characters.",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "minAlphaNumerals",
            "type": "integer",
            "multiValued": false,
            "description": "Minimum num of alphas and numeric chars.",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "minSpecialChars",
            "type": "integer",
            "multiValued": false,
            "description": "Minimum num of special chars.",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "maxSpecialChars",
            "type": "integer",
            "multiValued": false,
            "description": "Maximum number of special chars.",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "minUpperCase",
            "type": "integer",
            "multiValued": false,
            "description": "Minimum num of upper case chars.",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "minLowerCase",
            "type": "integer",
            "multiValued": false,
            "description": "Minimum num of lower case chars.",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "minUnique",
            "type": "integer",
            "multiValued": false,
            "description": "Minimum num of unique chars.",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "maxRepeatChars",
            "type": "integer",
            "multiValued": false,
            "description": "Max num of repeated chars.",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "startsWithAlphas",
            "type": "boolean",
            "multiValued": false,
            "description": "Indicates password must begin with alpha char",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "minUnicodeChars",
            "type": "integer",
            "multiValued": false,
            "description": "[TO BE DISCUSSED]",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "firstNameDisallowed",
            "type": "boolean",
            "multiValued": false,
            "description": "Indicates a sequence of characters matching the resource's name.givenName SHALL NOT be included in the password",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "lastNameDisallowed",
            "type": "boolean",
            "multiValued": false,
            "description": "Indicates a sequence of characters matching the resource's name.familyName SHALL NOT be included in the password",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "userNameDisallowed",
            "type": "boolean",
            "multiValued": false,
            "description": "Indicates a sequence of characters matching the resource's userName SHALL NOT be included in the password",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "minPasswordAgeInDays",
            "type": "integer",
            "multiValued": false,
            "description": "An Integer indicating the minimum age in days before the password MAY be changed.",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "warningAfterDays",
            "type": "integer",
            "multiValued": false,
            "description": "An Integer indicating the number of days after which a password reset warning will be issued.",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "expiresAfterDays",
            "type": "integer",
            "multiValued": false,
            "description": "An Integer indicating the numbers of days after which a password reset is required.",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "requiredChars",
            "type": "string",
            "multiValued": false,
            "description": "A String value whose contents indicates a set of characters that MUST appear, in any sequence, in a password value.",
            "required": false,
            "caseExact": true,
            "mutability": "readWrite",
            "returned": "never",
            "uniqueness": "none"
        },
        {
            "name": "disallowedChars",
            "type": "string",
            "multiValued": false,
            "description": "A String value whose contents indicates a set of characters that SHALL NOT appear, in a password value.",
            "required": false,
            "caseExact": true,
            "mutability": "readWrite",
            "returned": "never",
            "uniqueness": "none"
        },
        {
            "name": "disallowedSubstrings",
            "type": "string",
            "multiValued": true,
            "description": "A set of strings that SHALL not appear in a password value.",
            "required": false,
            "caseExact": true,
            "mutability": "readWrite",
            "returned": "never",
            "uniqueness": "none"
        },
        {
            "name": "dictionaryLocation",
            "type": "reference",
            "referenceTypes": [
                "reference"
            ],
            "multiValued": false,
            "description": "A Reference value containing the URI of a dictionary of words not allowed to appear within a password value.",
            "required": false,
            "caseExact": false,
            "mutability": "readWrite",
            "returned": "default",
            "uniqueness": "none"
        },
        {
            "name": "passwordHistorySize",
            "type": "integer",
            "multiValued": false,
            "description": "An Integer indicating the number of passwords that will be kept in history that may not be used as a password.",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "maxIncorrectAttempts",
            "type": "integer",
            "multiValued": false,
            "description": "An Integer representing the maximum number of failed logins before an account is locked.",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "lockOutDuration",
            "type": "integer",
            "multiValued": false,
            "description": "An integer indicating the number of minutes an account will be locked after maxIncorrectAttempts exceeded.",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "challengesEnabled",
            "type": "boolean",
            "multiValued": false,
            "description": "Indicates whether challenges may be used during authentication.",
            "required": false,
            "mutability": "readWrite",
            "returned": "default"
        },
        {
            "name": "challengePolicy",
            "type": "complex",
            "multiValued": false,
            "description": "A complex attribute that defines policy around challenges.",
            "required": true,
            "returned": "default",
            "mutability": "readWrite",
            "subAttributes": [
                {
                    "name": "source",
                    "type": "integer",
                    "multiValued": false,
                    "description": "A number value indicating the source for challenges. Valid values are: 0 - user. 1 - admin defined. 2 - both",
                    "required": true,
                    "mutability": "readWrite",
                    "returned": "default"
                },
                {
                    "name": "defaultQuestions",
                    "type": "string",
                    "multiValued": true,
                    "description": "A Multi-valued String attribute that contains one or more default question a subject may use when setting their challenge questions",
                    "required": false,
                    "caseExact": false,
                    "mutability": "readWrite",
                    "returned": "default",
                    "uniqueness": "none"
                },
                {
                    "name": "minQuestionCount",
                    "type": "integer",
                    "multiValued": false,
                    "description": "An Integer indicating the minimum number of challenge questions a subject MUST answer when setting challenge question answers.  A value of 0 or no value  indicates no minimum.",
                    "required": true,
                    "mutability": "readWrite",
                    "returned": "default"
                },
                {
                    "name": "minAnswerCount",
                    "type": "integer",
                    "multiValued": false,
                    "description": "An Integer indicating the mimimum number of challenge answers a subject MUST answer when attempting to reset their password via forgot password request.",
                    "required": true,
                    "mutability": "readWrite",
                    "returned": "default"
                },
                {
                    "name": "allAtOnce",
                    "type": "boolean",
                    "multiValued": false,
                    "description": "When true, the client UI will present all challengers in random order each time displayed. When false, the client UI will present one challenge question at a time where the subject MUST respond before the next is displayed.",
                    "required": true,
                    "mutability": "readWrite",
                    "returned": "default"
                },
                {
                    "name": "minResponseLength",
                    "type": "integer",
                    "multiValued": false,
                    "description": "An Integer indicating the minimum number of chars in a challenge response.  No value or a value of 0 indicates no minimum length (effectively 1)",
                    "required": true,
                    "mutability": "readWrite",
                    "returned": "default"
                },
                {
                    "name": "maxIncorrectAttempts",
                    "type": "integer",
                    "multiValued": false,
                    "description": "An Integer indicates the maximum number of failed reset password attempts using challenges. If any challenges are wrong in a reset attempt, the user's resetAttempts counter will be incremented by 1.  If resetAttempts is greater than maxIncorrectAttempts,the subject's account will be locked with a locked.reason value.",
                    "required": true,
                    "mutability": "readWrite",
                    "returned": "default"
                }
            ]
        }
    ],
    "meta": {
        "resourceType": "Schema",
        "location": "/v2/Schemas/urn:ietf:params:scim:schemas:core:2_0:policy:Password"
    }
}

And here the payload for a create PasswordPolicy call:

{
   "schemas":[
      "urn:ietf:params:scim:schemas:core:2_0:policy:Password"
   ],
   "name": "Simple PasswordPolicy",
   "description": "This is a simple PasswordPolicy",
   "minLength": 6
}
alelb commented 7 years ago

Hi @davidiamyou,

Any news about this issue? Have you been able to reproduce it? You can also add a integer custom attribute to the user schema for a quick check.

imulab commented 7 years ago

Sorry @alelb I saw your bug report, but I am just too busy on my school relevant work and is unable to set aside time to work on this thing. If this is critical to you, would you rather try to fix it yourself and give me a PR?

I will have more time to work on this project starting Jan 2018, this semester is just a disaster. Sorry again.

imulab commented 5 years ago

Hi @alelb

This issue will be resolved in v2. It will have a native JSON parsing mechanism which parses JSON data into Go types with the help of attributes in one pass (no more intermediate map container or use reflection). In this way, the number types will be definitive.

imulab commented 4 years ago

This bug is fixed in v2. We now have a direct JSON deserializer which does the work strictly according to the schema. No more relying on Go's reflect based deserialization.

However, I would like to include your case as an official test case. Hence, keeping this issue open till the password policy extension is implemented.

Removing the milestone for v2.0.0 as a result.