Skip to content

Commit 7549f5e

Browse files
Chronicle Teamcopybara-github
authored andcommitted
Adds sample Python script for CreateSubject API
PiperOrigin-RevId: 410333202
1 parent 94c232d commit 7549f5e

2 files changed

Lines changed: 210 additions & 0 deletions

File tree

access_control/create_subject.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/usr/bin/env python3
2+
3+
# Copyright 2021 Google LLC
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
"""Executable and reusable sample for creating a subject.
18+
19+
A subject can be an analyst or an Identity Provider (IdP) group.
20+
"""
21+
22+
import argparse
23+
import json
24+
import sys
25+
from typing import Any, Mapping
26+
from typing import Optional
27+
from typing import Sequence
28+
29+
from google.auth.transport import requests
30+
31+
from common import chronicle_auth
32+
from common import regions
33+
34+
CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
35+
36+
37+
def initialize_command_line_args(
38+
args: Optional[Sequence[str]] = None) -> Optional[argparse.Namespace]:
39+
"""Initializes and checks all the command-line arguments."""
40+
parser = argparse.ArgumentParser()
41+
chronicle_auth.add_argument_credentials_file(parser)
42+
regions.add_argument_region(parser)
43+
parser.add_argument(
44+
"-n", "--name", type=str, required=True, help="subject ID")
45+
parser.add_argument(
46+
"-rs",
47+
"--roles",
48+
type=str,
49+
required=True,
50+
help=("the role(s) the created subject must have"))
51+
52+
# No need for a sanity check for the subject name and roles because these
53+
# arguments convert the provided input into strings and accept a wide range
54+
# of values. If the subject name isn't passed in, the error will be thrown
55+
# from the argparse library.
56+
57+
return parser.parse_args(args)
58+
59+
60+
def create_subject(http_session: requests.AuthorizedSession, name: str,
61+
roles: Sequence[str]) -> Mapping[str, Sequence[Any]]:
62+
"""Creates a subject.
63+
64+
Args:
65+
http_session: Authorized session for HTTP requests.
66+
name: The ID of the subject to retrieve information about.
67+
roles: The role(s) the created subject must have.
68+
69+
Returns:
70+
Information about the newly created subject in the form:
71+
{
72+
"subject": {
73+
"name": "test@test.com",
74+
"type": "SUBJECT_TYPE_ANALYST",
75+
"roles": [
76+
{
77+
"name": "Test",
78+
"title": "Test role",
79+
"description": "The Test role",
80+
"createTime": "yyyy-mm-ddThh:mm:ss.ssssssZ",
81+
"isDefault": false,
82+
"permissions": [
83+
{
84+
"name": "Test",
85+
"title": "Test permission",
86+
"description": "The Test permission",
87+
"createTime": "yyyy-mm-ddThh:mm:ss.ssssssZ",
88+
},
89+
...
90+
]
91+
},
92+
...
93+
]
94+
}
95+
}
96+
97+
Raises:
98+
requests.exceptions.HTTPError: HTTP request resulted in an error
99+
(response.status_code >= 400).
100+
"""
101+
url = f"{CHRONICLE_API_BASE_URL}/v1/subjects/{name}"
102+
body = {
103+
"name": name,
104+
"roles": roles,
105+
}
106+
response = http_session.request("POST", url, json=body)
107+
108+
if response.status_code >= 400:
109+
print(response.text)
110+
response.raise_for_status()
111+
return response.json()
112+
113+
114+
if __name__ == "__main__":
115+
cli = initialize_command_line_args()
116+
if not cli:
117+
sys.exit(1) # A sanity check failed.
118+
119+
CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, cli.region)
120+
session = chronicle_auth.initialize_http_session(cli.credentials_file)
121+
print(
122+
json.dumps(
123+
create_subject(session, cli.name, cli.roles.split(",")), indent=2))
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Copyright 2021 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
"""Unit tests for the "create_subject" module."""
16+
17+
import unittest
18+
import argparse
19+
20+
from unittest import mock
21+
22+
from google.auth.transport import requests
23+
24+
from . import create_subject
25+
26+
27+
class CreateSubjectTest(unittest.TestCase):
28+
29+
def test_initialize_command_line_args(self):
30+
actual = create_subject.initialize_command_line_args(
31+
["--name=test@test.com", "--roles="])
32+
self.assertEqual(
33+
actual,
34+
argparse.Namespace(
35+
credentials_file=None, name="test@test.com", roles="", region="us"))
36+
37+
@mock.patch.object(requests, "AuthorizedSession", autospec=True)
38+
@mock.patch.object(requests.requests, "Response", autospec=True)
39+
def test_create_subject_error(self, mock_response, mock_session):
40+
mock_session.request.return_value = mock_response
41+
type(mock_response).status_code = mock.PropertyMock(return_value=400)
42+
mock_response.raise_for_status.side_effect = (
43+
requests.requests.exceptions.HTTPError())
44+
45+
with self.assertRaises(requests.requests.exceptions.HTTPError):
46+
create_subject.create_subject(mock_session, "", [])
47+
48+
@mock.patch.object(requests, "AuthorizedSession", autospec=True)
49+
@mock.patch.object(requests.requests, "Response", autospec=True)
50+
def test_create_subject(self, mock_response, mock_session):
51+
mock_session.request.return_value = mock_response
52+
type(mock_response).status_code = mock.PropertyMock(return_value=200)
53+
subject_id = "test@test.com"
54+
roles = ["Test"]
55+
expected = {
56+
"subject": {
57+
"name":
58+
subject_id,
59+
"type":
60+
"SUBJECT_TYPE_ANALYST",
61+
"roles": [{
62+
"name":
63+
"Test",
64+
"title":
65+
"Test role",
66+
"description":
67+
"The Test role",
68+
"createTime":
69+
"2020-11-05T00:00:00Z",
70+
"isDefault":
71+
False,
72+
"permissions": [{
73+
"name": "Test",
74+
"title": "Test permission",
75+
"description": "The Test permission",
76+
"createTime": "2020-11-05T00:00:00Z",
77+
},]
78+
},]
79+
},
80+
}
81+
mock_response.json.return_value = expected
82+
actual = create_subject.create_subject(mock_session, subject_id, roles)
83+
self.assertEqual(actual, expected)
84+
85+
86+
if __name__ == "__main__":
87+
unittest.main()

0 commit comments

Comments
 (0)