Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to create new user #47

Closed
m-moris opened this issue Dec 7, 2021 · 7 comments
Closed

Unable to create new user #47

m-moris opened this issue Dec 7, 2021 · 7 comments
Assignees
Labels
Needs Attention 👋 question Further information is requested

Comments

@m-moris
Copy link

m-moris commented Dec 7, 2021

I'm trying to create new user in Azure AD B2C with the following program, but I cannot create it.

        client := msgraphsdk.NewGraphServiceClient(adapter)

	uid := uuid.New().String()
	upn := uid + "@xxxxxx.onmicrosoft.com"
	user := graph.NewUser()
	user.SetId(&uid)
	user.SetDisplayName(ref("Shohei Ohtani"))
	user.SetUserPrincipalName(&upn)
	user.SetMailNickname(&upn)

	d := &u.UsersRequestBuilderPostOptions{
		Body: user,
		H: map[string]string{
			"Content-type": "application/json",
		},
	}
	result, err := client.Users().Post(d)

	if err != nil {
		fmt.Printf("Error create new user %v\n", err)
		return
	}
	spew.Dump(result)

The err is null, so it is not determined to be a direct error, but when I dump the result object, the error message is embedded. Is the program wrong? How can I create the correct user?

Unable to read JSON request payload. Please ensure Content-Type header is set and payload is of valid JSON format.

(*graph.User)(0xc0000a4700)({
 DirectoryObject: (graph.DirectoryObject) {
  Entity: (graph.Entity) {
   additionalData: (map[string]interface {}) (len=1) {
    (string) (len=5) "error": (map[string]*jsonserialization.JsonParseNode) (len=3) {
     (string) (len=4) "code": (*jsonserialization.JsonParseNode)(0xc0001205d0)({
      value: (*string)(0xc0001205c0)((len=10) "BadRequest")
     }),
     (string) (len=7) "message": (*jsonserialization.JsonParseNode)(0xc000120630)({
      value: (*string)(0xc000120620)((len=114) "Unable to read JSON request payload. Please ensure Content-Type header is set and payload is of valid JSON format.")
     }),
     (string) (len=10) "innerError": (*jsonserialization.JsonParseNode)(0xc000120780)({
      value: (map[string]*jsonserialization.JsonParseNode) (len=3) {
       (string) (len=4) "date": (*jsonserialization.JsonParseNode)(0xc0001206b0)({
        value: (*string)(0xc0001206a0)((len=19) "2021-12-07T01:16:05")
       }),
       (string) (len=10) "request-id": (*jsonserialization.JsonParseNode)(0xc000120710)({
        value: (*string)(0xc000120700)((len=36) "4b689739-151b-4f11-8da2-63a3919bb52e")
       }),
       (string) (len=17) "client-request-id": (*jsonserialization.JsonParseNode)(0xc000120770)({
        value: (*string)(0xc000120760)((len=36) "2a786207-25ef-4694-acc2-70c2d2c59b38")
       })
      }
     })
    }
   },
   id: (*string)(<nil>)
  },
  deletedDateTime: (*time.Time)(<nil>)
 },

Thank

@baywet baywet self-assigned this Dec 7, 2021
@baywet baywet added question Further information is requested Needs author feedback labels Dec 7, 2021
@baywet
Copy link
Member

baywet commented Dec 7, 2021

Hey @m-moris ,
Thanks for reaching out and for trying the SDK.

I don't think you need to set the user id, it'll be automatically generated by the service upon creation.
Also, I think you need to set the creation type and the signInNames but leave the UPN empty (auto-filled by the service) if we refer to this answer on stack overflow.
I think the signInNames are now called identities, and you can find some documentation on how they work here

Lastly, you don't need to set the content type manually, the SDK does that for you.

        client := msgraphsdk.NewGraphServiceClient(adapter)

	user := graph.NewUser()
	user.SetDisplayName(ref("Shohei Ohtani"))
        user.SetCreationType(ref("LocalAccount"))
        // user.SetIdentities()... depends on your scenario

	d := &u.UsersRequestBuilderPostOptions{
		Body: user,
	}
	result, err := client.Users().Post(d)

	if err != nil {
		fmt.Printf("Error create new user %v\n", err)
		return
	}
	spew.Dump(result)

Let me know if you have further questions, if the questions are about how the identities property work, the best thing to do is to reach out on the Microsoft Q&A forum so you get the exact API call payload that the service expects for your scenario, and then revert here so we can "translate" this into Go code.

@m-moris
Copy link
Author

m-moris commented Dec 7, 2021

Hi @baywet

Let me give you my background. I am rewriting a program that used to create users by directly calling the REST API into a program that uses the SDK.

The first sample I presented was wrong. The following is a sample that was able to create a user.

However, I believe there are some problems.

One is that err variable is nil when error occurs. Shouldn't an error be set if the user cannot be created?

The other is that setting the User structure always requires a pointer. It's hard to use boolean, String, etc. when pointers are always required, are there any plans to improve this?

	f := false
	t := true
	uid := uuid.New().String()
	upn := uid + "@xxxxx.onmicrosoft.com"
	user := graph.NewUser()
	user.SetDisplayName(ref("Shohei Ohtani"))
	user.SetUserPrincipalName(&upn)
	user.SetMailNickname(ref("Shohei"))
	user.SetAccountEnabled(&t)

	prof := graph.NewPasswordProfile()
	prof.SetForceChangePasswordNextSignIn(&f)
	prof.SetPassword(ref(uuid.New().String()))
	user.SetPasswordProfile(prof)

	d := &u.UsersRequestBuilderPostOptions{
		Body: user,
	}
	result, err := client.Users().Post(d)

	if err != nil {
		fmt.Printf("Error create new user %v\n", err)
		return
	}
	spew.Dump(result)

Thanks.

@m-moris
Copy link
Author

m-moris commented Dec 7, 2021

Add the problem mentioned earlier.

Even if the user is created correctly, the Result object does not appear to have any value set. For example, when I call result.GetMailNickname(), the value is NIL.

@m-moris
Copy link
Author

m-moris commented Dec 7, 2021

It may be the default behavior for result.GetMailNickName() to result in NIL

@baywet
Copy link
Member

baywet commented Dec 7, 2021

Thanks for clarifying.

Errors not being returned when the service returns a error

We have a known limitation on deserializing and returning errors, we're planning to address this by GA.

Pointers everywhere

When serializing/deserializing the SDK needs to be able to convey the difference of information between "the value is set to the default" and "the value is not set".
If we removed all the pointers for scalar values, creating a new user and not setting the displayName for example would result in the serializer sending "displayName": "" to the service, and either having the service return a 400 saying you can't set an empty display name or having an empty (not null) display name for the user.
This is obviously an issue for multiple reasons:

  • More data on the wire (all the unset properties would now be present with the default)
  • The default for go could be different from the service default, leading to undesired behavior

And the same challenge arises the other way around, the SDK user needs to be able to tell the difference between the service didn't return anything for this property and the service returned a default value.
Now that we've established we need a way to tell the ability to tell that difference, let's look at alternatives:

  • pointers, what we have today
  • value types, where each type would be wrapped in something like "NilableString" with a value field, and either a pointer for that field or a boolean "isnil" field that would need to be kept in sync. We evaluated that, and it brings additional complexity without delivering additional value.
  • another solution we haven't thought of and you might suggest?

Now what we could do to make people's lives easier is provide a bunch of functions like "RefString" "RefBoolean" accepting a scalar value as argument (no pointer) and returning a pointer to it. This way people wouldn't have to create a variable and reference it.
But to be honest, this feels like a feature that should be in the language/compiler, there's no reason why you shouldn't be able to write user.SetDisplayName(&"Jane Doe") and have the compiler understand you're just trying to save time here, insert a variable just before the statement/block, assign it and reference it for you.

MailNickName property is nil

I'm not sure what's the behavior of the service here, using a REST call, do you always get a value on the POST call for that property? It might be that value is not populated (stored in the data store), or not returned by default.

@m-moris
Copy link
Author

m-moris commented Dec 8, 2021

Hi @baywet

about pointer

I am just starting to use the Go language, so I don't have any idea about the desired specification. I agree with you that this is a feature that should be provided by the compiler.

response value

I was mistaken because some properties do not return a value by default. DisplayName is correctly included in the response.

Thanks a lot of support. I'm looing forward GA!

@baywet
Copy link
Member

baywet commented Dec 8, 2021

Thanks for all the feedback you've provided so far. I think all the discussions on this thread are resolved. Closing

@baywet baywet closed this as completed Dec 8, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Attention 👋 question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants