2

Trying to use Google admin directory API in order to read members of a google group (organization) - it works fine. When I try to add a member I get:

{ errors: 
   [ { domain: 'global',
       reason: 'required',
       message: 'Missing required field: member' } ],
  code: 400,
  message: 'Missing required field: member' }

I've googled the error and found questions like this, this and a few other unhelpful results.

I checked and it's definitely not a missing scope nor permissions.

#!/usr/bin/python
import httplib2
import json
from oauth2client.client import SignedJwtAssertionCredentials
from urllib import urlencode


def get_group_members(group):
    url = 'https://www.googleapis.com/admin/directory/v1/groups/{}/members'.format(group['email'])
    return call_google_api("GET", url)


def add_group_member(group, payload=False):
    url = 'https://www.googleapis.com/admin/directory/v1/groups/{}/members'.format(group)
    return call_google_api("POST", url, payload)


def call_google_api(method, url, payload=False):
    content = {}
    try:
        http = get_conn()
        if payload:
            (resp, content) = http.request(uri=url, method=method, body=urlencode(payload))
        else:
            (resp, content) = http.request(uri=url, method=method)
    except Exception as e:
        print "Failed to post request to [{}] due to: {}".format(url, e)
    return json.loads(content)

def get_conn():
    client_email = get_client_email_from_db()    
    with open(get_private_key_filename()) as f:
        private_key = f.read()

    oauth_scope = ['https://www.googleapis.com/auth/admin.directory.group.member', 
                  'https://www.googleapis.com/auth/admin.directory.group',
    ]

    credentials = SignedJwtAssertionCredentials(client_email, private_key, oauth_scope, sub='googleapi@organization.com')
    http = httplib2.Http()
    return credentials.authorize(http)


if __name__ == '__main__':
    payload = {
        "email": "test-user@organization.com",
        "role": "MEMBER",
    }
    print "\n ---------------------------------- \n"
    print "calling add_group_member('test-user@organization.com', 'test-group@organization.com')"
    res = add_group_member("test-group@organization.com", payload)
    print "\n ---------------------------------- \n"

Comment:
I managed to achieve what I wanted by using the sdk apiclient.discovery.build, but still - I'm curious, what's the issue and if it can be solved.

Debugging the request:

connect: (www.googleapis.com, 443)
send: 'POST /admin/directory/v1/groups/xxxx@xxxx.com/members HTTP/1.1\r\nHost: www.googleapis.com\r\nContent-Length: 38\r\ncontent-type: application/json\r\naccept-encoding: gzip, deflate\r\nauthorization: Bearer ya29.RAFzf3hyxvP0LuR4VdpqKr_dD0WzOcvXjn4eWV5Em6xJDissi4ieOZ2ZBRMOP-WLhvTrecBxgF_6sznc1GKSWHanvgYTh_EzcilsAN0f5jOiiMahOadG2v5ixBPL9GcqebRdz_kQc1y2iQ\r\nuser-agent: Python-httplib2/0.9 (gzip)\r\n\r\nrole=MEMBER&email=alfasi%40xxxx.com'
reply: 'HTTP/1.1 400 Bad Request\r\n'
header: Vary: Origin
header: Vary: X-Origin
header: Content-Type: application/json; charset=UTF-8
header: Content-Encoding: gzip
header: Date: Sat, 28 Mar 2015 23:14:47 GMT
header: Expires: Sat, 28 Mar 2015 23:14:47 GMT
header: Cache-Control: private, max-age=0
header: X-Content-Type-Options: nosniff
header: X-Frame-Options: SAMEORIGIN
header: X-XSS-Protection: 1; mode=block
header: Server: GSE
header: Alternate-Protocol: 443:quic,p=0.5
header: Transfer-Encoding: chunked
Community
  • 1
  • 1
Nir Alfasi
  • 53,191
  • 11
  • 86
  • 129

2 Answers2

3

Since google APIs use (only?) JSON encoding, your post data is not being parsed into the needed member object. You are already loading json for the response, so you should just need to change the encoding, and optionally indicate it explicitly:

 if payload:
        (resp, content) = http.request(uri=url, method=method, body=urlencode(payload))
 # becomes:   
 if payload:
        (resp, content) = http.request(uri=url, method=method, body=json.dumps(payload), headers={'Content-type':'application/json'})
lossleader
  • 13,182
  • 2
  • 31
  • 45
  • Adding these headers I get a new error: `This API does not support parsing form-encoded input.`... – Nir Alfasi Mar 25 '15 at 02:49
  • @alfasin, Ah I assumed they would also support another encoding when explicitly indicated.. But it sounds like json or nothing, so I've edited the answer. – lossleader Mar 25 '15 at 09:32
  • Since I can replicate the error in the explorer by leaving member as empty {}, it seems like it is still something with parsing the body. I would check the exact request contents using httplib2.debuglevel (https://developers.google.com/api-client-library/python/guide/logging) – lossleader Mar 25 '15 at 22:11
  • It prints the headers and the POST request (looks like calling curl) - but it doesn't provide more information. – Nir Alfasi Mar 28 '15 at 22:15
  • If you set debug to 4 or above, it should be showing you the body of the request which needs to be valid JSON, the content-length header should say 57? in your above example (you may need to adjust for the actual member email) and the content-type should be 'application/json'.. – lossleader Mar 28 '15 at 22:50
  • So here's a weird thing, it sends: `connect: (accounts.google.com, 443) send: 'POST /o/oauth2/token HTTP/1.1\r\nHost: accounts.google.com\r\nContent-Length: 806\r\ncontent-type: application/x-www-form-urlencoded\r\...` but later on in the headers section it prints: `header: Content-Type: application/json; charset=utf-8` – Nir Alfasi Mar 28 '15 at 22:54
  • The first one looks like the oauth which I think is urlencoded, hopefully there is a second connect: and send: right before the bad reply: ? – lossleader Mar 28 '15 at 23:11
  • Oh you're right I missed it, yes there are two sends (oauth, req). – Nir Alfasi Mar 28 '15 at 23:16
  • I added the debug print of the second request to the question – Nir Alfasi Mar 28 '15 at 23:19
  • Looking at the debug, it like you didn't switch urlencode() to json.dumps()? – lossleader Mar 28 '15 at 23:20
  • I have no clue why it didn't work the first time I tried it... but holy cr@p, now it works... you just got yourself an extra 110 reps buddy - thanks so much! – Nir Alfasi Mar 28 '15 at 23:25
  • 1
    I'd be curious to know if the content-type isn't optional as it should be since JSON is their only working format, but either way that is a very sensitive API.. Cheers, :) – lossleader Mar 28 '15 at 23:33
0

If you are trying to add a user to a group the first time, the role must be MEMBER. If it is anything other than that it gives you this error - Missing required field: member.

So first add the user as MEMBER and then specify any other role.

Sayali
  • 356
  • 1
  • 2
  • 13
  • That was not the issue, I wasn't able to add a MEMBER as well. Now I can add an OWNER on the first try (see accepted answer for more details). – Nir Alfasi Apr 10 '15 at 01:38