mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-05 14:51:39 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdc763c91a | ||
|
|
2ec19cc197 | ||
|
|
387ad7f9d9 | ||
|
|
ed8af7d81c | ||
|
|
d955f49684 | ||
|
|
bcd75a979f | ||
|
|
5cc46fc4ee |
14
.github/ISSUE_TEMPLATE.txt
vendored
14
.github/ISSUE_TEMPLATE.txt
vendored
@@ -1,14 +0,0 @@
|
||||
The issue tracker is for reporting product deficiencies. "How do I?" questions should be posted to the discussion forum at https://groups.google.com/group/google-apps-manager. When in doubt, start at the discussion forum and return here only when instructed to do so.
|
||||
|
||||
Please confirm the following:
|
||||
* I have upgraded to the latest GAM release from https://github.com/GAM-team/GAM/releases and I still have this issue.
|
||||
* I am typing the command as described in the GAM Wiki at https://github.com/GAM-team/GAM/wiki
|
||||
|
||||
Full steps to reproduce the issue:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
Expected outcome (what are you trying to do?):
|
||||
|
||||
Actual outcome (what errors or bad behavior do you see instead?):
|
||||
14
.github/ISSUE_TEMPLATE/aa-question.md
vendored
14
.github/ISSUE_TEMPLATE/aa-question.md
vendored
@@ -1,14 +0,0 @@
|
||||
---
|
||||
name: Question about using GAM
|
||||
about: Help with using GAM or running it for the first time
|
||||
title: Please use the GAM discussion group
|
||||
labels: invalid
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
If you need help with GAM, please do not file an issue here, it will be closed and ignored.
|
||||
|
||||
Please post your question to the GAM discussion group where other admins are ready and willing to help:
|
||||
|
||||
https://groups.google.com/g/google-apps-manager
|
||||
23
.github/ISSUE_TEMPLATE/za-bug-report.md
vendored
23
.github/ISSUE_TEMPLATE/za-bug-report.md
vendored
@@ -1,23 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: jay0lee
|
||||
|
||||
---
|
||||
|
||||
The issue tracker is for reporting product deficiencies. "How do I?" questions should be posted to the discussion forum at https://groups.google.com/group/google-apps-manager. When in doubt, start at the discussion forum and return here only when instructed to do so.
|
||||
|
||||
Please confirm the following:
|
||||
* I have upgraded to the latest GAM release from https://github.com/GAM-team/GAM/releases and I still have this issue.
|
||||
* I am typing the command as described in the GAM Wiki at https://github.com/GAM-team/GAM/wiki.
|
||||
|
||||
Full steps to reproduce the issue:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
Expected outcome (what are you trying to do?):
|
||||
|
||||
Actual outcome (what errors or bad behavior do you see instead?):
|
||||
20
.github/ISSUE_TEMPLATE/zz-feature-request.md
vendored
20
.github/ISSUE_TEMPLATE/zz-feature-request.md
vendored
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for GAM
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: jay0lee
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
19
.github/actions/decrypt.sh
vendored
19
.github/actions/decrypt.sh
vendored
@@ -1,19 +0,0 @@
|
||||
#!/bin/sh
|
||||
credspath="$1"
|
||||
if [ ! -d "$credspath" ]; then
|
||||
echo "creating ${credspath}"
|
||||
mkdir -p "$credspath"
|
||||
fi
|
||||
credsfile="${credspath}/oauth2.txt"
|
||||
echo "$oa2" > "$credsfile"
|
||||
echo "File size:"
|
||||
wc -c "$credsfile"
|
||||
echo "File type:"
|
||||
file "$credsfile"
|
||||
echo "Validation:"
|
||||
jq -e . "$credsfile" > /dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Valid JSON"
|
||||
else
|
||||
echo "Invalid JSON"
|
||||
fi
|
||||
13
.github/actions/entitlements.plist
vendored
13
.github/actions/entitlements.plist
vendored
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- These are required for binaries built by PyInstaller -->
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
6
.github/actions/package_exclusions.txt
vendored
6
.github/actions/package_exclusions.txt
vendored
@@ -1,6 +0,0 @@
|
||||
oauth2.txt
|
||||
nobrowser.txt
|
||||
enabledasa.txt
|
||||
lastupdatecheck.txt
|
||||
*.lck
|
||||
*.csv
|
||||
61
.github/stale.yml
vendored
61
.github/stale.yml
vendored
@@ -1,61 +0,0 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||
daysUntilStale: 90
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||
daysUntilClose: 7
|
||||
|
||||
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
||||
onlyLabels: []
|
||||
|
||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- enhancement
|
||||
- help wanted
|
||||
- security
|
||||
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: false
|
||||
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: false
|
||||
|
||||
# Set to true to ignore issues with an assignee (defaults to false)
|
||||
exemptAssignees: false
|
||||
|
||||
# Label to use when marking as stale
|
||||
staleLabel: wontfix
|
||||
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs.
|
||||
|
||||
# Comment to post when removing the stale label.
|
||||
# unmarkComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Comment to post when closing a stale Issue or Pull Request.
|
||||
# closeComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 30
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
# only: issues
|
||||
|
||||
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
|
||||
# pulls:
|
||||
# daysUntilStale: 30
|
||||
# markComment: >
|
||||
# This pull request has been automatically marked as stale because it has not had
|
||||
# recent activity. It will be closed if no further activity occurs. Thank you
|
||||
# for your contributions.
|
||||
|
||||
# issues:
|
||||
# exemptLabels:
|
||||
# - confirmed
|
||||
1160
.github/workflows/build.yml
vendored
1160
.github/workflows/build.yml
vendored
File diff suppressed because it is too large
Load Diff
69
.github/workflows/codeql-analysis.yml
vendored
69
.github/workflows/codeql-analysis.yml
vendored
@@ -1,69 +0,0 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'wiki/**'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'wiki/**'
|
||||
schedule:
|
||||
- cron: '25 10 * * 1'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'python' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
93
.github/workflows/get-cacerts.yml
vendored
93
.github/workflows/get-cacerts.yml
vendored
@@ -1,93 +0,0 @@
|
||||
name: Check for Google Root CA Updates
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '23 23 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: src/gam
|
||||
|
||||
jobs:
|
||||
check-certs:
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token
|
||||
fetch-depth: 0 # otherwise, you will failed to push refs to dest repo
|
||||
|
||||
- name: Get Current cacerts.pem hash
|
||||
run: |
|
||||
export CURRENT_HASH=$(sha256sum ./cacerts.pem)
|
||||
echo "Current hash is: ${CURRENT_HASH}"
|
||||
echo "CURRENT_HASH=${CURRENT_HASH}" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate GAM-specific bundle with LE + Google roots
|
||||
run: |
|
||||
OUTPUT_FILE="cacerts.pem"
|
||||
> "$OUTPUT_FILE"
|
||||
|
||||
process_cert() {
|
||||
local url="$1"
|
||||
local op_ca="$2"
|
||||
local label="$3"
|
||||
local tmp_cert=$(mktemp)
|
||||
curl "$url" > "$tmp_cert"
|
||||
local issuer=$(openssl x509 -noout -issuer -in "$tmp_cert" | sed -e 's/^issuer= *//')
|
||||
local subject=$(openssl x509 -noout -subject -in "$tmp_cert" | sed -e 's/^subject= *//')
|
||||
local serial_hex=$(openssl x509 -noout -serial -in "$tmp_cert" | sed -e 's/^serial=//')
|
||||
local serial_dec=$(python3 -c "print(int('$serial_hex', 16))")
|
||||
local md5=$(openssl x509 -noout -fingerprint -md5 -in "$tmp_cert" | sed -e 's/.*=//' | tr '[:upper:]' '[:lower:]')
|
||||
local sha1=$(openssl x509 -noout -fingerprint -sha1 -in "$tmp_cert" | sed -e 's/.*=//' | tr '[:upper:]' '[:lower:]')
|
||||
local sha256=$(openssl x509 -noout -fingerprint -sha256 -in "$tmp_cert" | sed -e 's/.*=//' | tr '[:upper:]' '[:lower:]')
|
||||
echo "# Operating CA: $op_ca" >> "$OUTPUT_FILE"
|
||||
echo "# Issuer: $issuer" >> "$OUTPUT_FILE"
|
||||
echo "# Subject: $subject" >> "$OUTPUT_FILE"
|
||||
echo "# Label: \"$label\"" >> "$OUTPUT_FILE"
|
||||
echo "# Serial: $serial_dec" >> "$OUTPUT_FILE"
|
||||
echo "# MD5 Fingerprint: $md5" >> "$OUTPUT_FILE"
|
||||
echo "# SHA1 Fingerprint: $sha1" >> "$OUTPUT_FILE"
|
||||
echo "# SHA256 Fingerprint: $sha256" >> "$OUTPUT_FILE"
|
||||
cat "$tmp_cert" >> "$OUTPUT_FILE"
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
rm "$tmp_cert"
|
||||
}
|
||||
|
||||
echo "#" >> "$OUTPUT_FILE"
|
||||
echo "# This is a custom certificate authority bundle for GAM" >> "$OUTPUT_FILE"
|
||||
echo "# It's composed of Let's Encrypt Root CAs and Google's" >> "$OUTPUT_FILE"
|
||||
echo "# certificate bundle. This should be the minimal list of" >> "$OUTPUT_FILE"
|
||||
echo "# CAs required to talk to Google and Github." >> "$OUTPUT_FILE"
|
||||
echo"" >> "$OUTPUT_FILE"
|
||||
echo "Processing Let's Encrypt ISRG Root X1..."
|
||||
process_cert "https://letsencrypt.org/certs/isrgrootx1.pem" "Let's Encrypt" "ISRG Root X1"
|
||||
echo "Processing Let's Encrypt ISRG Root X2..."
|
||||
process_cert "https://letsencrypt.org/certs/isrg-root-x2.pem" "Let's Encrypt" "ISRG Root X2"
|
||||
|
||||
echo "Appending Google's roots.pem..."
|
||||
curl -s https://pki.goog/roots.pem >> "$OUTPUT_FILE"
|
||||
echo "Done! The new bundle has been saved to $OUTPUT_FILE."
|
||||
|
||||
- name: Compare hashes
|
||||
run: |
|
||||
export NEW_HASH=$(sha256sum ./cacerts.pem)
|
||||
if [ "$NEW_HASH" == "$CURRENT_HASH" ]; then
|
||||
echo "Same file."
|
||||
else
|
||||
echo "New file content. Was ${CURRENT_HASH} and now is ${NEW_HASH}"
|
||||
fi
|
||||
|
||||
- name: Commit file
|
||||
run: |
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
git add cacerts.pem
|
||||
git diff --quiet && git diff --staged --quiet || git commit -am '[ci skip] Updated cacerts.pem'
|
||||
|
||||
- name: Push changes
|
||||
uses: ad-m/github-push-action@77c5b412c50b723d2a4fbc6d71fb5723bcd439aa
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
43
.github/workflows/pushwiki.yml
vendored
43
.github/workflows/pushwiki.yml
vendored
@@ -1,43 +0,0 @@
|
||||
name: Push wiki
|
||||
permissions:
|
||||
contents: write
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'wiki/**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
pushwiki:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout GAM source
|
||||
run: |
|
||||
# checkout via normal shell here
|
||||
# so we're not authorized to actually make any changes
|
||||
git clone https://github.com/GAM-team/GAM
|
||||
|
||||
- name: Checkout Wiki source
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
with:
|
||||
path: GAM.wiki
|
||||
repository: GAM-team/GAM.wiki
|
||||
persist-credentials: true
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Overwrite all Wiki repo files with /wiki from main git
|
||||
run: |
|
||||
# remove all wiki repo files so deletes work
|
||||
rm -fv GAM.wiki/*.md
|
||||
# copy all files from main GAM repo wiki folder
|
||||
cp -fv GAM/wiki/*.md GAM.wiki/
|
||||
|
||||
- name: Commit Wiki changes
|
||||
run: |
|
||||
cd GAM.wiki
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
git add -A
|
||||
git commit -m "[no ci] Push Wiki changes"
|
||||
git status
|
||||
git push
|
||||
35
.github/workflows/pypi.yml
vendored
35
.github/workflows/pypi.yml
vendored
@@ -1,35 +0,0 @@
|
||||
name: build and publish releases to PyPi
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
pypi:
|
||||
name: Upload release to PyPI
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/p/gam7
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install required packages to publish
|
||||
run: |
|
||||
python3 -m pip install --upgrade build
|
||||
|
||||
- name: Build packages
|
||||
run: |
|
||||
python -m build
|
||||
|
||||
- name: Publish package distributions to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
|
||||
with:
|
||||
attestation: true
|
||||
144
.github/workflows/upgrade_deps.yml
vendored
144
.github/workflows/upgrade_deps.yml
vendored
@@ -1,144 +0,0 @@
|
||||
name: Daily Dependency Pinning (2-Week Buffer)
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Runs every day at midnight UTC
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch: # Allows you to trigger it manually from the UI
|
||||
|
||||
jobs:
|
||||
pin-deps:
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
|
||||
with:
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Calculate and pin two-week old stable versions
|
||||
shell: python
|
||||
run: |
|
||||
import json
|
||||
import urllib.request
|
||||
import re
|
||||
import tomllib
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
toml_path = Path("pyproject.toml")
|
||||
if not toml_path.exists():
|
||||
print("pyproject.toml not found!")
|
||||
exit(1)
|
||||
|
||||
content = toml_path.read_text(encoding="utf-8")
|
||||
parsed_toml = tomllib.loads(content)
|
||||
|
||||
target_deps = set()
|
||||
|
||||
# Standard [project.dependencies]
|
||||
target_deps.update(parsed_toml.get("project", {}).get("dependencies", []))
|
||||
|
||||
# Optional [project.optional-dependencies]
|
||||
for deps in parsed_toml.get("project", {}).get("optional-dependencies", {}).values():
|
||||
target_deps.update(deps)
|
||||
|
||||
# Dev [dependency-groups] (uv / PEP 735 standard)
|
||||
for deps in parsed_toml.get("dependency-groups", {}).values():
|
||||
if isinstance(deps, list):
|
||||
for d in deps:
|
||||
if isinstance(d, str):
|
||||
target_deps.add(d)
|
||||
|
||||
if not target_deps:
|
||||
print("No dependencies found to process.")
|
||||
exit(0)
|
||||
|
||||
updates = {}
|
||||
two_weeks_ago = datetime.now(timezone.utc) - timedelta(days=14)
|
||||
print(f"Evaluating dependencies against cutoff date: {two_weeks_ago.isoformat()}")
|
||||
|
||||
for dep in target_deps:
|
||||
# Isolate base package name (e.g., "yubikey-manager>=5.6.1" -> "yubikey-manager")
|
||||
pkg_name = re.split(r'[<>=!~;\s]', dep)[0].strip()
|
||||
|
||||
# Preserve environment markers if they exist
|
||||
marker = ""
|
||||
if ";" in dep:
|
||||
marker = " ; " + dep.split(";", 1)[1].strip()
|
||||
|
||||
print(f"Fetching PyPI data for: {pkg_name}")
|
||||
try:
|
||||
url = f"https://pypi.org/pypi/{pkg_name}/json"
|
||||
req = urllib.request.Request(url, headers={'User-Agent': 'GAM-CI-Script'})
|
||||
with urllib.request.urlopen(req) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
|
||||
valid_versions = []
|
||||
for ver, files in data.get("releases", {}).items():
|
||||
if not files:
|
||||
continue
|
||||
|
||||
upload_time_str = files[0].get("upload_time_iso_8601")
|
||||
if not upload_time_str:
|
||||
continue
|
||||
|
||||
if upload_time_str.endswith('Z'):
|
||||
upload_time_str = upload_time_str[:-1] + '+00:00'
|
||||
|
||||
upload_time = datetime.fromisoformat(upload_time_str)
|
||||
|
||||
# Filter: Must be older than 2 weeks and not a pre-release
|
||||
if upload_time <= two_weeks_ago and not any(x in ver.lower() for x in ['a', 'b', 'rc', 'dev']):
|
||||
valid_versions.append((upload_time, ver))
|
||||
|
||||
if valid_versions:
|
||||
# Sort by upload time descending to get the newest valid option
|
||||
valid_versions.sort(key=lambda x: x[0], reverse=True)
|
||||
target_version = valid_versions[0][1]
|
||||
|
||||
pinned_dep = f"{pkg_name}=={target_version}{marker}"
|
||||
if pinned_dep != dep:
|
||||
updates[dep] = pinned_dep
|
||||
print(f" -> Pinning: '{dep}' => '{pinned_dep}'")
|
||||
else:
|
||||
print(f" -> Already pinned correctly to {target_version}")
|
||||
else:
|
||||
print(f" -> No valid historical versions found.")
|
||||
|
||||
except urllib.error.HTTPError as e:
|
||||
print(f" -> Package not found on PyPI or HTTP error: {e}")
|
||||
except Exception as e:
|
||||
print(f" -> Error processing {pkg_name}: {e}")
|
||||
|
||||
# 3. Replace the strings safely in the original file content
|
||||
new_content = content
|
||||
for old_dep, new_dep in updates.items():
|
||||
# Regex targets the exact string inside either single or double quotes
|
||||
# Using a lambda replacement ensures we don't trip over escape sequences in the new string
|
||||
escaped_old = re.escape(old_dep)
|
||||
pattern = r'([\'"])' + escaped_old + r'\1'
|
||||
new_content = re.sub(pattern, lambda m: m.group(1) + new_dep + m.group(1), new_content)
|
||||
|
||||
# Write changes back to pyproject.toml
|
||||
if content != new_content:
|
||||
toml_path.write_text(new_content, encoding="utf-8")
|
||||
print("\npyproject.toml updated successfully.")
|
||||
else:
|
||||
print("\nNo updates required.")
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: "chore: upgrade PyPi deps"
|
||||
title: "Upgrade PyPi deps"
|
||||
body: "Automated scan checking PyPI for package versions at least 2 weeks old."
|
||||
branch: sys-deps-upgrade
|
||||
force: false # Standard push, plays nice with rulesets
|
||||
201
LICENSE
201
LICENSE
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
71
README.md
71
README.md
@@ -1,46 +1,43 @@
|
||||
GAM is a command line tool for Google Workspace admins to manage domain and user settings quickly and easily.
|
||||
GAM
|
||||
============================
|
||||
GAM is a free, open source command line tool for
|
||||
Google Apps Administrators to manage
|
||||
domain and user settings quickly and easily. GAM supports
|
||||
|
||||
[](https://github.com/GAM-team/GAM/actions/workflows/build.yml)
|
||||
* creating, deleting, and updating users, aliases, groups,
|
||||
organizations, and resource calendars
|
||||
* modifying user email settings such as IMAP, signatures,
|
||||
vacation messages, profile sharing, email forwarding,
|
||||
send as address, labels, and features.
|
||||
* modifying calendar access rights for users and resource calendars.
|
||||
* generating detailed reports for users, groups, resources,
|
||||
account activity, email clients, and quotas.
|
||||
* and many more commands
|
||||
|
||||
# Quick Start
|
||||
|
||||
## Linux / MacOS
|
||||
|
||||
Open a terminal and run:
|
||||
|
||||
```sh
|
||||
bash <(curl -s -S -L https://gam-shortn.appspot.com/gam-install)
|
||||
```
|
||||
|
||||
this will download GAM, install it and start setup.
|
||||
|
||||
## Windows
|
||||
|
||||
Download the EXE Installer from the [GitHub Releases] page. Run it and you'll be prompted to setup GAM.
|
||||
|
||||
## Use your own Python
|
||||
If you'd prefer to install GAM as a Python package you can install with pip:
|
||||
```
|
||||
pip install gam7
|
||||
```
|
||||
# Documentation
|
||||
Downloads
|
||||
---------
|
||||
You can download the current GAM release from
|
||||
the [GitHub Releases] page.
|
||||
|
||||
Documentation
|
||||
------------------
|
||||
The GAM documentation is hosted in the [GitHub Wiki]
|
||||
|
||||
# Mailing List / Discussion group
|
||||
Mailing List / Discussion group
|
||||
-------------------------------
|
||||
The GAM mailing list / discussion group is hosted
|
||||
on [Google Groups]. You can join the list and interact
|
||||
via email, or just post from the web itself.
|
||||
|
||||
The GAM mailing list / discussion group is hosted on [Google Groups]. You can join the list and interact via email, or just post from the web itself.
|
||||
Source Repository
|
||||
-----------------
|
||||
The official GAM source repository is on [GitHub] in the master branch.
|
||||
|
||||
# Chat Room
|
||||
Author
|
||||
------
|
||||
GAM is maintained by <a href="mailto:jay0lee@gmail.com">Jay Lee</a>.
|
||||
|
||||
There is a public chat room hosted in Google Chat. [Instructions to join](https://github.com/GAM-team/GAM/wiki/GAM-Public-Chat-Room).
|
||||
|
||||
# Author
|
||||
|
||||
GAM is maintained by [Jay (James) Lee](mailto:jay0lee@gmail.com) and [Ross Scroggs](mailto:ross.scroggs@gmail.com). Please direct "how do I?" questions to [Google Groups].
|
||||
|
||||
[GAM release]: https://github.com/GAM-team/GAM/releases
|
||||
[GitHub Releases]: https://github.com/GAM-team/GAM/releases
|
||||
[GitHub]: https://github.com/GAM-team/GAM/tree/master
|
||||
[GitHub Wiki]: https://github.com/GAM-team/GAM/wiki/
|
||||
[GitHub Releases]: https://github.com/jay0lee/GAM/releases
|
||||
[GitHub]: https://github.com/jay0lee/GAM/tree/master
|
||||
[GitHub Wiki]: https://github.com/jay0lee/GAM/wiki/
|
||||
[Google Groups]: http://groups.google.com/group/google-apps-manager
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# overrides uv.lock to force newer dependencies
|
||||
# when old deps are vulnerable. These should be set
|
||||
# to expire after 2 weeks when the fixed version will
|
||||
# be automatically picked up anyway.
|
||||
# Format: package_requirement | MM/DD/YYYY
|
||||
urllib3>=2.7.0 | 05/22/2026
|
||||
@@ -1,72 +0,0 @@
|
||||
[project]
|
||||
name = "gam7"
|
||||
dynamic = [
|
||||
"version",
|
||||
]
|
||||
authors = [
|
||||
{ name = "Jay Lee", email = "jay0lee@gmail.com" },
|
||||
{ name = "Ross Scroggs", email = "Ross.Scroggs@gmail.com" },
|
||||
]
|
||||
dependencies = [
|
||||
"arrow==1.4.0",
|
||||
"chardet==7.4.3",
|
||||
"cryptography==48.0.0",
|
||||
"distro==1.9.0 ; sys_platform=='linux'",
|
||||
"filelock==3.29.0",
|
||||
"google-api-python-client==2.196.0",
|
||||
"google-auth-httplib2==0.4.0",
|
||||
"google-auth-oauthlib==1.4.0",
|
||||
"google-auth==2.53.0",
|
||||
"httplib2==0.31.2",
|
||||
"lxml==6.1.1",
|
||||
"passlib==1.7.4",
|
||||
"pathvalidate==3.3.1",
|
||||
"pysocks==1.7.1",
|
||||
]
|
||||
description = "CLI tool to manage Google Workspace"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Programming Language :: Python :: 3.14",
|
||||
"Operating System :: OS Independent",
|
||||
]
|
||||
license-files = [
|
||||
"LICEN[CS]E*",
|
||||
]
|
||||
|
||||
[project.license]
|
||||
text = "Apache License (2.0)"
|
||||
|
||||
[project.optional-dependencies]
|
||||
yubikey = [
|
||||
"yubikey-manager==5.9.1",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
gam = "gam.__main__:main"
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/GAM-team/GAM"
|
||||
Issues = "https://github.com/GAM-team/GAM/issues"
|
||||
Discussion = "https://groups.google.com/group/google-apps-manager"
|
||||
Chat = "https://git.io/gam-chat"
|
||||
|
||||
[tool.hatch.version]
|
||||
path = "src/gam/__init__.py"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = [
|
||||
"src/gam",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = [
|
||||
"hatchling",
|
||||
]
|
||||
build-backend = "hatchling.build"
|
||||
72
src/.gitignore
vendored
72
src/.gitignore
vendored
@@ -1,72 +0,0 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
bin/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
# Mr Developer
|
||||
.mr.developer.cfg
|
||||
.project
|
||||
.pydevproject
|
||||
|
||||
# Rope
|
||||
.ropeproject
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
*.pot
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# GAM specific
|
||||
client_secrets.json
|
||||
oauth2.txt*
|
||||
oauth2service*
|
||||
debug.gam
|
||||
lastupdatecheck.txt
|
||||
noupdatecheck.txt
|
||||
nodito.txt
|
||||
nobrowser.txt
|
||||
nocache.txt
|
||||
noverifyssl.txt
|
||||
gamcache/
|
||||
dist/
|
||||
gam-64/
|
||||
*.zip
|
||||
*.msi
|
||||
*.wixobj
|
||||
*.wixpdb
|
||||
9052
src/GamCommands.txt
9052
src/GamCommands.txt
File diff suppressed because it is too large
Load Diff
21281
src/GamUpdate.txt
21281
src/GamUpdate.txt
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
../LICENSE
|
||||
@@ -1 +0,0 @@
|
||||
../README.md
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Sample Python script to call GAM"""
|
||||
|
||||
import multiprocessing
|
||||
import platform
|
||||
|
||||
from gam import initializeLogging, CallGAMCommand
|
||||
|
||||
if __name__ == '__main__':
|
||||
# One time initialization
|
||||
if platform.system() != 'Linux':
|
||||
multiprocessing.freeze_support()
|
||||
multiprocessing.set_start_method('spawn', force=True)
|
||||
initializeLogging()
|
||||
#
|
||||
CallGAMCommand(['gam', 'version'])
|
||||
# Issue command, output goes to stdout/stderr
|
||||
rc = CallGAMCommand(['gam', 'info', 'domain'])
|
||||
# Issue command, redirect stdout/stderr
|
||||
rc = CallGAMCommand(['gam', 'redirect', 'stdout', 'domain.txt', 'redirect', 'stderr', 'stdout', 'info', 'domain'])
|
||||
@@ -1,482 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
usage()
|
||||
{
|
||||
cat << EOF
|
||||
GAM installation script.
|
||||
|
||||
OPTIONS:
|
||||
-h show help.
|
||||
-d Directory where gam folder will be installed. Default is \$HOME/bin/
|
||||
-a Architecture to install (x86_64, arm64). Default is to detect your arch with "uname -m".
|
||||
-o OS we are running (linux, macos). Default is to detect your OS with "uname -s".
|
||||
-b OS version. Default is to detect on macOS and Linux.
|
||||
-l Just upgrade GAM to latest version. Skips project creation and auth.
|
||||
-p Profile update (true, false). Should script add gam command to environment. Default is true.
|
||||
-u Admin user email address to use with GAM. Default is to prompt.
|
||||
-r Regular user email address. Used to test service account access to user data. Default is to prompt.
|
||||
-v Version to install (latest, prerelease, draft, 3.8, etc). Default is latest.
|
||||
-s Strip gam component from extracted files, files will be downloaded directly to $target_dir
|
||||
EOF
|
||||
}
|
||||
|
||||
target_dir="$HOME/bin"
|
||||
target_folder="$target_dir/gam7"
|
||||
gamarch=$(uname -m)
|
||||
gamos=$(uname -s)
|
||||
osversion=""
|
||||
update_profile=true
|
||||
upgrade_only=false
|
||||
gamversion="latest"
|
||||
adminuser=""
|
||||
regularuser=""
|
||||
strip_gam="--strip-components 0"
|
||||
|
||||
while getopts "hd:a:o:b:lp:u:r:v:s" OPTION
|
||||
do
|
||||
case $OPTION in
|
||||
h) usage; exit;;
|
||||
d) target_dir="${OPTARG%/}"; target_folder="$target_dir/gam7";;
|
||||
a) gamarch="$OPTARG";;
|
||||
o) gamos="$OPTARG";;
|
||||
b) osversion="$OPTARG";;
|
||||
l) upgrade_only=true;;
|
||||
p) update_profile="$OPTARG";;
|
||||
u) adminuser="$OPTARG";;
|
||||
r) regularuser="$OPTARG";;
|
||||
v) gamversion="$OPTARG";;
|
||||
s) strip_gam="--strip-components 1"; target_folder="$target_dir";;
|
||||
?) usage; exit;;
|
||||
esac
|
||||
done
|
||||
target_gam="$target_folder/gam"
|
||||
|
||||
update_profile() {
|
||||
[ "$2" -eq 1 ] || [ -f "$1" ] || return 1
|
||||
|
||||
grep -F "$alias_line" "$1" > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
echo_yellow "Adding gam alias to profile file $1."
|
||||
echo -e "\n$alias_line" >> "$1"
|
||||
else
|
||||
echo_yellow "gam alias already exists in profile file $1. Skipping add."
|
||||
fi
|
||||
}
|
||||
|
||||
echo_red()
|
||||
{
|
||||
echo -e "\x1B[1;31m$1"
|
||||
echo -e '\x1B[0m'
|
||||
}
|
||||
|
||||
echo_green()
|
||||
{
|
||||
echo -e "\x1B[1;32m$1"
|
||||
echo -e '\x1B[0m'
|
||||
}
|
||||
|
||||
echo_yellow()
|
||||
{
|
||||
echo -e "\x1B[1;33m$1"
|
||||
echo -e '\x1B[0m'
|
||||
}
|
||||
|
||||
version_gt()
|
||||
{
|
||||
if [ "${1}" = "${2}" ]; then
|
||||
true
|
||||
else
|
||||
test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$gamversion" == "latest" ]; then
|
||||
release_url="https://api.github.com/repos/GAM-team/GAM/releases/latest"
|
||||
elif [ "$gamversion" == "prerelease" -o "$gamversion" == "draft" ]; then
|
||||
release_url="https://api.github.com/repos/GAM-team/GAM/releases"
|
||||
else
|
||||
release_url="https://api.github.com/repos/GAM-team/GAM/releases/tags/v$gamversion"
|
||||
fi
|
||||
|
||||
if [ -z ${GHCLIENT+x} ]; then
|
||||
check_type="unauthenticated"
|
||||
curl_opts=( )
|
||||
else
|
||||
check_type="authenticated"
|
||||
curl_opts=( "$GHCLIENT" )
|
||||
fi
|
||||
curl_ver=$(curl --version|head -1|cut -d " " -f 2)
|
||||
if [[ "${curl_ver:0:4}" < "7.76" ]]; then
|
||||
curl_fail=( )
|
||||
else
|
||||
curl_fail=( "--fail-with-body" )
|
||||
fi
|
||||
echo_yellow "Checking GitHub URL $release_url for $gamversion GAM release ($check_type)..."
|
||||
release_json=$(curl \
|
||||
--silent \
|
||||
"${curl_opts[@]}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"$release_url" \
|
||||
"${curl_fail[@]}")
|
||||
curl_exit_code=$?
|
||||
if [ $curl_exit_code -ne 0 ]; then
|
||||
echo_red "ERROR retrieving URL: ${release_json}"
|
||||
exit
|
||||
else
|
||||
echo_green "done"
|
||||
fi
|
||||
|
||||
echo_yellow "Calculating download URL for this device..."
|
||||
# Python is sadly the nearest to universal way to safely handle JSON with Bash
|
||||
# At least this code should be compatible with just about any Python version ever
|
||||
# unlike GAM itself. If some users don't have Python we can try grep / sed / etc
|
||||
# but that gets really ugly
|
||||
pycode="import json
|
||||
import sys
|
||||
|
||||
attrib = sys.argv[1]
|
||||
gamversion = sys.argv[2]
|
||||
|
||||
release = json.load(sys.stdin)
|
||||
if type(release) is list:
|
||||
for a_release in release:
|
||||
if a_release['prerelease'] and gamversion != 'prerelease':
|
||||
continue
|
||||
elif a_release['draft'] and gamversion != 'draft':
|
||||
continue
|
||||
release = a_release
|
||||
break
|
||||
try:
|
||||
for asset in release['assets']:
|
||||
print(asset[attrib])
|
||||
#else:
|
||||
# print('ERROR: Attribute: {0} for version {1} not found'.format(attrib, gamversion))
|
||||
except KeyError:
|
||||
print('ERROR: assets value not found in JSON value of:\n\n%s' % release)"
|
||||
|
||||
pycmd="python3"
|
||||
$pycmd -V >/dev/null 2>&1
|
||||
rc=$?
|
||||
if (( $rc != 0 )); then
|
||||
pycmd="python"
|
||||
fi
|
||||
$pycmd -V >/dev/null 2>&1
|
||||
rc=$?
|
||||
if (( $rc != 0 )); then
|
||||
pycmd="/usr/bin/python3"
|
||||
fi
|
||||
$pycmd -V >/dev/null 2>&1
|
||||
rc=$?
|
||||
if (( $rc != 0 )); then
|
||||
pycmd="python2"
|
||||
fi
|
||||
$pycmd -V >/dev/null 2>&1
|
||||
rc=$?
|
||||
if (( $rc != 0 )); then
|
||||
echo_red "ERROR: No version of python installed."
|
||||
exit
|
||||
fi
|
||||
# also sort the URLs once so we're evaluating newest OS version first
|
||||
download_urls=$(echo "$release_json" | \
|
||||
$pycmd -c "$pycode" browser_download_url "$gamversion" | \
|
||||
sort --version-sort --reverse)
|
||||
if [[ ${download_urls:0:5} = "ERROR" ]]; then
|
||||
echo_red "${download_urls}"
|
||||
exit
|
||||
fi
|
||||
|
||||
case $gamos in
|
||||
[lL]inux)
|
||||
gamos="linux"
|
||||
download_urls=$(echo -e "$download_urls" | grep -e "-linux-")
|
||||
if [ "$osversion" == "" ]; then
|
||||
this_glibc_ver=$(ldd --version | awk '/ldd/{print $NF}')
|
||||
else
|
||||
this_glibc_ver=$osversion
|
||||
fi
|
||||
echo "This Linux distribution uses glibc $this_glibc_ver"
|
||||
case $gamarch in
|
||||
x86_64)
|
||||
download_urls=$(echo -e "$download_urls" | grep -e "-x86_64-")
|
||||
gam_x86_64_glibc_vers=$(echo -e "$download_urls" | \
|
||||
grep --only-matching 'glibc[0-9\.]*\.tar\.xz$' \
|
||||
| cut -c 6-9 )
|
||||
useglibc="legacy"
|
||||
for gam_glibc_ver in $gam_x86_64_glibc_vers; do
|
||||
if version_gt $this_glibc_ver $gam_glibc_ver; then
|
||||
useglibc="glibc$gam_glibc_ver"
|
||||
echo_green "Using GAM compiled against $useglibc"
|
||||
break
|
||||
fi
|
||||
done
|
||||
download_url=$(echo -e "$download_urls" | grep "$useglibc")
|
||||
;;
|
||||
arm|arm64|aarch64)
|
||||
download_urls=$(echo -e "$download_urls" | grep -e "-arm64-\|-aarch64-")
|
||||
gam_arm64_glibc_vers=$(echo -e "$download_urls" | \
|
||||
grep --only-matching 'glibc[0-9\.]*\.tar\.xz$' | \
|
||||
cut -c 6-9)
|
||||
useglibc="legacy"
|
||||
for gam_glibc_ver in $gam_arm64_glibc_vers; do
|
||||
if version_gt $this_glibc_ver $gam_glibc_ver; then
|
||||
useglibc="glibc$gam_glibc_ver"
|
||||
echo_green "Using GAM compiled against $useglibc"
|
||||
break
|
||||
fi
|
||||
done
|
||||
download_url=$(echo -e "$download_urls" | grep "$useglibc")
|
||||
;;
|
||||
*)
|
||||
echo_red "ERROR: this installer currently only supports x86_64 and arm64 Linux. Looks like you're running on $gamarch. Exiting."
|
||||
exit
|
||||
esac
|
||||
;;
|
||||
[Mm]ac[Oo][sS]|[Dd]arwin)
|
||||
gamos="macos"
|
||||
currentversion=$(sw_vers -productVersion | awk -F '.' '{print $1 "." $2}')
|
||||
# override osversion only if it wasn't set by cli arguments
|
||||
osversion=${osversion:-${currentversion}}
|
||||
# override osversion only if it wasn't set by cli arguments
|
||||
download_urls=$(echo -e "$download_urls" | grep -e "-macos")
|
||||
case $gamarch in
|
||||
x86_64)
|
||||
archgrep="-x86_64"
|
||||
;;
|
||||
arm|arm64|aarch64)
|
||||
archgrep="-arm64\|-aarch64"
|
||||
;;
|
||||
*)
|
||||
echo_red "ERROR: this installer currently only supports x86_64 and arm64 macOS. Looks like you're running on ${gamarch}. Exiting."
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
gam_macos_urls=$(echo -e "$download_urls" | \
|
||||
grep -e $archgrep)
|
||||
versionless_urls=$(echo -e "$gam_macos_urls" | \
|
||||
grep -e "-macos-")
|
||||
if [ "$versionless_urls" == "" ]; then
|
||||
# versions after 7.00.38 include macOS version info
|
||||
gam_macos_vers=$(echo -e "$gam_macos_urls" | \
|
||||
grep --only-matching -e '-macos[0-9\.]*' | \
|
||||
cut -c 7-10)
|
||||
for gam_mac_ver in $gam_macos_vers; do
|
||||
if version_gt $currentversion $gam_mac_ver; then
|
||||
download_url=$(echo -e "$gam_macos_urls" | grep "$gam_mac_ver")
|
||||
echo_green "You are running macOS ${currentversion} Using GAM compiled against ${gam_mac_ver}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -z ${download_url+x} ]; then
|
||||
echo_red "Sorry, you are running macOS ${osversion} but GAM on ${gamarch} requires macOS ${gam_mac_ver} or newer. Exiting."
|
||||
exit
|
||||
fi
|
||||
else
|
||||
# versions 7.00.38 and older don't include version info
|
||||
case $gamarch in
|
||||
x86_64)
|
||||
minimum_version=13
|
||||
;;
|
||||
arm|arm64|aarch64)
|
||||
minimum_version=14
|
||||
;;
|
||||
esac
|
||||
download_url=$(echo -e "$download_urls" | grep -e $archgrep)
|
||||
if version_gt "$osversion" "$minimum_version"; then
|
||||
echo_green "You are running macOS ${osversion}, good. Downloading GAM from ${download_url}."
|
||||
else
|
||||
echo_red "Sorry, you are running macOS ${osversion} but GAM on ${gamarch} requires macOS ${minimum_version}. Exiting."
|
||||
exit
|
||||
fi
|
||||
if [ -z ${download_url+x} ]; then
|
||||
echo_red "Sorry, you are running macOS ${currentversion} but GAM on ${gamarch} requires macOS ${minimum_version}. Exiting."
|
||||
exit
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
MINGW64_NT*)
|
||||
gamos="windows"
|
||||
echo "You are running Windows"
|
||||
download_url=$(echo -e "$download_urls" | \
|
||||
grep -e "-windows-" | \
|
||||
grep ".zip")
|
||||
;;
|
||||
*)
|
||||
echo_red "Sorry, this installer currently only supports Linux and macOS. Looks like you're running on ${gamos}. Exiting."
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
|
||||
# Temp dir for archive
|
||||
temp_archive_dir=$(mktemp -d 2>/dev/null || mktemp -d -t 'mytmpdir')
|
||||
|
||||
# Clean up after ourselves even if we are killed with CTRL-C
|
||||
trap "rm -rf $temp_archive_dir" EXIT
|
||||
|
||||
# hack to grab the end of the URL which should be the filename.
|
||||
name=$(echo -e "$download_url" | rev | cut -f1 -d "/" | rev)
|
||||
|
||||
echo_yellow "Downloading ${download_url} to $temp_archive_dir ($check_type)..."
|
||||
# Save archive to temp w/o losing our path
|
||||
(cd "$temp_archive_dir" && curl -O -L -s "${curl_opts[@]}" "$download_url")
|
||||
|
||||
mkdir -p "$target_folder"
|
||||
echo_yellow "Deleting contents of $target_folder/lib"
|
||||
rm -frv "$target_folder/lib"
|
||||
|
||||
echo_yellow "Extracting archive to $target_dir"
|
||||
if [[ "$name" =~ tar.xz|tar.gz|tar ]]; then
|
||||
tar $strip_gam -xf "$temp_archive_dir"/"$name" -C "$target_dir"
|
||||
elif [[ "$name" == *.zip ]]; then
|
||||
unzip -o "${temp_archive_dir}/${name}" -d "${target_dir}"
|
||||
else
|
||||
echo "I don't know what to do with files like ${name}. Giving up."
|
||||
exit 1
|
||||
fi
|
||||
rc=$?
|
||||
if (( $rc != 0 )); then
|
||||
echo_red "ERROR: extracting the GAM archive with tar failed with error $rc. Exiting."
|
||||
exit
|
||||
else
|
||||
echo_green "Finished extracting GAM archive."
|
||||
fi
|
||||
|
||||
# Update profile to add gam command
|
||||
if [ "$update_profile" = true ]; then
|
||||
alias_line="alias gam=\"$target_gam\""
|
||||
if [ "$gamos" == "linux" ]; then
|
||||
update_profile "$HOME/.bash_aliases" 0 || update_profile "$HOME/.bash_profile" 0 || update_profile "$HOME/.bashrc" 0
|
||||
update_profile "$HOME/.zshrc" 0
|
||||
elif [ "$gamos" == "macos" ]; then
|
||||
update_profile "$HOME/.bash_aliases" 0 || update_profile "$HOME/.bash_profile" 0 || update_profile "$HOME/.bashrc" 0 || update_profile "$HOME/.profile" 1
|
||||
update_profile "$HOME/.zshrc" 1
|
||||
fi
|
||||
else
|
||||
echo_yellow "skipping profile update."
|
||||
fi
|
||||
|
||||
if [ "$upgrade_only" = true ]; then
|
||||
echo_green "Here's information about your GAM upgrade:"
|
||||
"$target_gam" version extended
|
||||
rc=$?
|
||||
if (( $rc != 0 )); then
|
||||
echo_red "ERROR: Failed running GAM for the first time with return code $rc. Please report this error to GAM mailing list. Exiting."
|
||||
exit
|
||||
fi
|
||||
|
||||
echo_green "GAM upgrade complete!"
|
||||
exit
|
||||
fi
|
||||
|
||||
while true; do
|
||||
read -p "Can you run a full browser on this machine? (usually Y for macOS, N for Linux if you SSH into this machine) " yn
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
"$target_gam" config no_browser false save
|
||||
break
|
||||
;;
|
||||
[Nn]*)
|
||||
"$target_gam" config no_browser true save
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo_red "Please answer yes or no."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
echo
|
||||
|
||||
project_created=false
|
||||
while true; do
|
||||
read -p "GAM is now installed. Are you ready to set up a Google API project for GAM? (yes or no) " yn
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
if [ "$adminuser" == "" ]; then
|
||||
read -p "Please enter your Google Workspace admin email address: " adminuser
|
||||
fi
|
||||
"$target_gam" create project $adminuser
|
||||
rc=$?
|
||||
if (( $rc == 0 )); then
|
||||
echo_green "Project creation complete."
|
||||
project_created=true
|
||||
break
|
||||
else
|
||||
echo_red "Project creation failed. Trying again. Say N to skip project creation."
|
||||
fi
|
||||
;;
|
||||
[Nn]*)
|
||||
echo -e "\nYou can create an API project later by running:\n\ngam create project\n"
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo_red "Please answer yes or no."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
admin_authorized=false
|
||||
while $project_created; do
|
||||
read -p "Are you ready to authorize GAM to perform Google Workspace management operations as your admin account? (yes or no) " yn
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
"$target_gam" oauth create $adminuser
|
||||
rc=$?
|
||||
if (( $rc == 0 )); then
|
||||
echo_green "Admin authorization complete."
|
||||
admin_authorized=true
|
||||
break
|
||||
else
|
||||
echo_red "Admin authorization failed. Trying again. Say N to skip admin authorization."
|
||||
fi
|
||||
;;
|
||||
[Nn]*)
|
||||
echo -e "\nYou can authorize an admin later by running:\n\ngam oauth create\n"
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo_red "Please answer yes or no."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
service_account_authorized=false
|
||||
while $admin_authorized; do
|
||||
read -p "Are you ready to authorize GAM to manage Google Workspace user data and settings? (yes or no) " yn
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
if [ "$regularuser" == "" ]; then
|
||||
read -p "Please enter the email address of a regular Google Workspace user: " regularuser
|
||||
fi
|
||||
echo_yellow "Great! Checking service account scopes.This will fail the first time. Follow the steps to authorize and retry. It can take a few minutes for scopes to PASS after they've been authorized in the admin console."
|
||||
"$target_gam" user $regularuser check serviceaccount
|
||||
rc=$?
|
||||
if (( $rc == 0 )); then
|
||||
echo_green "Service account authorization complete."
|
||||
service_account_authorized=true
|
||||
break
|
||||
else
|
||||
echo_red "Service account authorization failed. Confirm you entered the scopes correctly in the admin console. It can take a few minutes for scopes to PASS after they are entered in the admin console so if you're sure you entered them correctly, go grab a coffee and then hit Y to try again. Say N to skip admin authorization."
|
||||
fi
|
||||
;;
|
||||
[Nn]*)
|
||||
echo -e "\nYou can authorize a service account later by running:\n\ngam user $adminuser check serviceaccount\n"
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo_red "Please answer yes or no."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo_green "Here's information about your new GAM installation:"
|
||||
"$target_gam" version extended
|
||||
rc=$?
|
||||
if (( $rc != 0 )); then
|
||||
echo_red "ERROR: Failed running GAM for the first time with $rc. Please report this error to GAM mailing list. Exiting."
|
||||
exit
|
||||
fi
|
||||
|
||||
echo_green "GAM installation and setup complete!"
|
||||
if [ "$update_profile" = true ]; then
|
||||
echo_green "Please restart your terminal shell or to get started right away run:\n\n$alias_line"
|
||||
fi
|
||||
@@ -1,88 +0,0 @@
|
||||
:neworupgrade
|
||||
@echo.
|
||||
@set /p nu= "If you have installed any version of GAM on any computer for your domain, enter u to upgrade, otherwise enter n? [u or n] "
|
||||
@if /I "%nu%"=="u" (
|
||||
@ echo GAM installation and setup complete!
|
||||
@ goto alldone
|
||||
)
|
||||
@if /I not "%nu%"=="n" (
|
||||
@ echo.
|
||||
@ echo Please answer n or u.
|
||||
@ goto neworupgrade
|
||||
)
|
||||
|
||||
:createproject
|
||||
@echo.
|
||||
@set /p yn= "Are you ready to set up a Google API project for GAM? [y or n] "
|
||||
@if /I "%yn%"=="n" (
|
||||
@ echo.
|
||||
@ echo You can create an API project later by running:
|
||||
@ echo.
|
||||
@ echo gam create project
|
||||
@ goto alldone
|
||||
)
|
||||
@if /I not "%yn%"=="y" (
|
||||
@ echo.
|
||||
@ echo Please answer y or n.
|
||||
@ goto createproject
|
||||
)
|
||||
|
||||
@set /p adminemail= "Please enter your Google Workspace admin email address: "
|
||||
|
||||
@gam create project %adminemail%
|
||||
@if not ERRORLEVEL 1 goto projectdone
|
||||
@echo.
|
||||
@echo Project creation failed. Trying again. Say n to skip project creation.
|
||||
@goto createproject
|
||||
:projectdone
|
||||
|
||||
:adminauth
|
||||
@echo.
|
||||
@set /p yn= "Are you ready to authorize GAM to perform Google Workspace management operations as your admin account? [y or n] "
|
||||
@if /I "%yn%"=="n" (
|
||||
@ echo.
|
||||
@ echo You can authorize an admin later by running:
|
||||
@ echo.
|
||||
@ echo gam oauth create %adminemail%
|
||||
@ goto admindone
|
||||
)
|
||||
@if /I not "%yn%"=="y" (
|
||||
@ echo.
|
||||
@ echo Please answer y or n.
|
||||
@ goto adminauth
|
||||
)
|
||||
@gam oauth create %adminemail%
|
||||
@if not ERRORLEVEL 1 goto admindone
|
||||
@echo.
|
||||
@echo Admin authorization failed. Trying again. Say n to skip admin authorization.
|
||||
@goto adminauth
|
||||
:admindone
|
||||
|
||||
:saauth
|
||||
@echo.
|
||||
@set /p yn= "Are you ready to authorize GAM to manage Google Workspace user data and settings? [y or n] "
|
||||
@if /I "%yn%"=="n" (
|
||||
@ echo.
|
||||
@ echo You can authorize a service account later by running:
|
||||
@ echo.
|
||||
@ echo gam user %adminemail% check serviceaccount
|
||||
@ goto sadone
|
||||
)
|
||||
@if /I not "%yn%"=="y" (
|
||||
@ echo.
|
||||
@ echo Please answer y or n.
|
||||
@ goto saauth
|
||||
)
|
||||
@echo.
|
||||
@set /p regularuser= "Please enter the email address of a regular Google Workspace user: "
|
||||
@echo Great! Checking service account scopes. This will fail the first time. Follow the steps to authorize and retry. It can take a few minutes for scopes to PASS after they've been authorized in the admin console.
|
||||
@gam user %regularuser% check serviceaccount
|
||||
@if not ERRORLEVEL 1 goto sadone
|
||||
@echo.
|
||||
@echo Service account authorization failed. Confirm you entered the scopes correctly in the admin console. It can take a few minutes for scopes to PASS after they are entered in the admin console so if you're sure you entered them correctly, go grab a coffee and then hit Y to try again. Say N to skip admin authorization.
|
||||
@goto saauth
|
||||
:sadone
|
||||
|
||||
@echo GAM installation and setup complete!
|
||||
:alldone
|
||||
@pause
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 8.1 / Server 2012 R2 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
|
||||
<!-- Windows 10+ / Server 2016+ -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
116
src/gam.iss
116
src/gam.iss
@@ -1,116 +0,0 @@
|
||||
; --- 1. PREPROCESSOR DEFINITIONS ---
|
||||
#define AppVersion GetEnv("GAMVERSION")
|
||||
#if AppVersion == ""
|
||||
#define AppVersion "7.0.0"
|
||||
#endif
|
||||
|
||||
; Pull architecture directly from GitHub Actions environment variable
|
||||
#define RunnerArch GetEnv("RUNNER_ARCH")
|
||||
|
||||
[Setup]
|
||||
; --- 2. CORE APPLICATION INFO ---
|
||||
AppId={{D86B52B2-EFE9-4F9D-8BA3-9D84B9B2D319}
|
||||
AppName=GAM7
|
||||
AppVersion={#AppVersion}
|
||||
AppPublisher=GAM Team - google-apps-manager@googlegroups.com
|
||||
DefaultDirName={sd}\GAM7
|
||||
LicenseFile=dist\gam\gam7\LICENSE
|
||||
PrivilegesRequired=admin
|
||||
ChangesEnvironment=yes
|
||||
|
||||
; Tell Inno Setup to use a custom signtool defined via the command line
|
||||
SignTool=gamsigntool
|
||||
|
||||
; --- 3. COMPRESSION & OPTIMIZATION ---
|
||||
Compression=lzma2/ultra64
|
||||
SolidCompression=yes
|
||||
|
||||
; --- 4. DYNAMIC ARCHITECTURE CONFIGURATION ---
|
||||
; GitHub Actions RUNNER_ARCH is typically uppercase "ARM64" or "X64"
|
||||
#if RunnerArch == "ARM64" || RunnerArch == "arm64"
|
||||
ArchitecturesAllowed=arm64
|
||||
ArchitecturesInstallIn64BitMode=arm64
|
||||
OutputBaseFilename=gam-{#AppVersion}-windows-arm64
|
||||
#else
|
||||
ArchitecturesAllowed=x64compatible
|
||||
ArchitecturesInstallIn64BitMode=x64compatible
|
||||
OutputBaseFilename=gam-{#AppVersion}-windows-x86_64
|
||||
#endif
|
||||
|
||||
[Messages]
|
||||
; Custom error if an admin tries to run the ARM64 installer on an Intel machine
|
||||
#if RunnerArch == "ARM64" || RunnerArch == "arm64"
|
||||
WindowsVersionNotSupported=You downloaded the ARM64 version of GAM, but this computer has an Intel or AMD processor.%n%nPlease go back to the release page and download the x86_64 installer instead.
|
||||
#endif
|
||||
|
||||
[Files]
|
||||
; --- 5. DYNAMIC FILE INCLUSION ---
|
||||
Source: "dist\gam\gam7\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
|
||||
[Registry]
|
||||
; --- 6. PATH ENVIRONMENT VARIABLE ---
|
||||
Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
|
||||
ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}"; \
|
||||
Check: NeedsAddPath(ExpandConstant('{app}'))
|
||||
|
||||
[Code]
|
||||
const
|
||||
ERROR_SUCCESS = 0;
|
||||
|
||||
function MsiEnumRelatedProducts(lpUpgradeCode: string; dwReserved: Integer; iProductIndex: Integer; lpProductBuf: string): Integer;
|
||||
external 'MsiEnumRelatedProductsW@msi.dll stdcall';
|
||||
|
||||
function UninstallWixMSI(): Boolean;
|
||||
var
|
||||
UpgradeCode: string;
|
||||
ProductCode: string;
|
||||
ResultCode: Integer;
|
||||
begin
|
||||
UpgradeCode := '{D86B52B2-EFE9-4F9D-8BA3-9D84B9B2D319}';
|
||||
ProductCode := StringOfChar(' ', 39);
|
||||
|
||||
ResultCode := MsiEnumRelatedProducts(UpgradeCode, 0, 0, ProductCode);
|
||||
|
||||
if ResultCode = ERROR_SUCCESS then
|
||||
begin
|
||||
ProductCode := Trim(ProductCode);
|
||||
Exec('msiexec.exe', '/x ' + ProductCode + ' /qn /norestart', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
||||
end;
|
||||
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function InitializeSetup(): Boolean;
|
||||
begin
|
||||
// --- Architecture Warning for Emulation ---
|
||||
#if RunnerArch != "ARM64" && RunnerArch != "arm64"
|
||||
if IsArm64() then
|
||||
begin
|
||||
if MsgBox('Notice: You are installing the Intel (x86_64) build of GAM on an ARM processor.' + #13#10#13#10 +
|
||||
'While this will work via Windows emulation, it will perform worse than the native ARM64 version.' + #13#10#13#10 +
|
||||
'Do you want to continue with the installation anyway?',
|
||||
mbConfirmation, MB_YESNO) = idNo then
|
||||
begin
|
||||
Result := False;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
#endif
|
||||
|
||||
UninstallWixMSI();
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function NeedsAddPath(Param: string): boolean;
|
||||
var
|
||||
OrigPath: string;
|
||||
begin
|
||||
if not RegQueryStringValue(HKEY_LOCAL_MACHINE,
|
||||
'SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
|
||||
'Path', OrigPath)
|
||||
then begin
|
||||
Result := True;
|
||||
exit;
|
||||
end;
|
||||
Result := Pos(';' + Param + ';', ';' + OrigPath + ';') = 0;
|
||||
end;
|
||||
17
src/gam.py
17
src/gam.py
@@ -1,17 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Provides backwards compatibility for calling gam as a single .py file"""
|
||||
|
||||
import multiprocessing
|
||||
import platform
|
||||
|
||||
from gam.__main__ import main
|
||||
|
||||
# Run from command line
|
||||
if __name__ == '__main__':
|
||||
if platform.system() != 'Linux':
|
||||
multiprocessing.freeze_support()
|
||||
# Python 3.14.4 and PyInstaller 6.19.0 don't play nice with Linux forkserver
|
||||
# use spawn everywhere for now.
|
||||
multiprocessing.set_start_method('spawn', force=True)
|
||||
main()
|
||||
155
src/gam.spec
155
src/gam.spec
@@ -1,155 +0,0 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
from os import getenv
|
||||
import re
|
||||
from sys import platform
|
||||
|
||||
from PyInstaller.utils.hooks import copy_metadata
|
||||
|
||||
from gam.gamlib.glverlibs import GAM_VER_LIBS
|
||||
|
||||
|
||||
with open("gam/__init__.py") as f:
|
||||
version_file = f.read()
|
||||
version = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M).group(1)
|
||||
version_list = [int(i) for i in version.split('.')]
|
||||
while len(version_list) < 4:
|
||||
version_list.append(0)
|
||||
version_tuple = tuple(version_list)
|
||||
version_str = str(version_tuple)
|
||||
with open("version_info.txt.in") as f:
|
||||
version_info = f.read()
|
||||
version_info = version_info.replace("{VERSION}", version).replace(
|
||||
"{VERSION_TUPLE}", version_str
|
||||
)
|
||||
with open("version_info.txt", "w") as f:
|
||||
f.write(version_info)
|
||||
print(version_info)
|
||||
|
||||
datas = []
|
||||
for pkg in GAM_VER_LIBS:
|
||||
datas += copy_metadata(pkg, recursive=True)
|
||||
datas += [('gam/cbcm-v1.1beta1.json', '.')]
|
||||
datas += [('gam/contactdelegation-v1.json', '.')]
|
||||
datas += [('gam/datastudio-v1.json', '.')]
|
||||
datas += [('gam/meet-v2beta.json', '.')]
|
||||
datas += [('gam/serviceaccountlookup-v1.json', '.')]
|
||||
hiddenimports = [
|
||||
'gam.gamlib.yubikey',
|
||||
]
|
||||
|
||||
excludes = [
|
||||
'pkg_resources',
|
||||
]
|
||||
|
||||
runtime_hooks = []
|
||||
a = Analysis(
|
||||
['gam/__main__.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=datas,
|
||||
hiddenimports=hiddenimports,
|
||||
hookspath=['tools/hooks'],
|
||||
hooksconfig={},
|
||||
runtime_hooks=runtime_hooks,
|
||||
excludes=excludes,
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=None,
|
||||
noarchive=False,
|
||||
)
|
||||
#print(f"datas from analysis:\n{a.datas}")
|
||||
for d in a.datas:
|
||||
if 'pyconfig' in d[0]:
|
||||
a.datas.remove(d)
|
||||
break
|
||||
#print(f"datas after pyconfig cleanup:\n{a.datas}")
|
||||
pyz = PYZ(a.pure,
|
||||
a.zipped_data,
|
||||
cipher=None)
|
||||
# requires Python 3.10+ but no one should be compiling
|
||||
# GAM with older versions anyway
|
||||
target_arch = None
|
||||
codesign_identity = None
|
||||
entitlements_file = None
|
||||
manifest = None
|
||||
version = 'version_info.txt'
|
||||
match platform:
|
||||
case "darwin":
|
||||
if getenv('arch') == 'universal2':
|
||||
target_arch = "universal2"
|
||||
|
||||
codesign_identity = getenv('codesign_identity')
|
||||
if codesign_identity:
|
||||
entitlements_file = '../.github/actions/entitlements.plist'
|
||||
strip = True
|
||||
case "win32":
|
||||
target_arch = None
|
||||
strip = False
|
||||
manifest = 'gam.exe.manifest'
|
||||
case _:
|
||||
target_arch = None
|
||||
strip = True
|
||||
name = 'gam'
|
||||
debug = False
|
||||
bootloader_ignore_signals = False
|
||||
upx = False
|
||||
console = True
|
||||
disable_windowed_traceback = False
|
||||
argv_emulation = False
|
||||
if getenv('PYINSTALLER_BUILD_ONEDIR') == 'yes':
|
||||
# Build one directory
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name=name,
|
||||
debug=debug,
|
||||
bootloader_ignore_signals=bootloader_ignore_signals,
|
||||
strip=strip,
|
||||
manifest=manifest,
|
||||
upx=upx,
|
||||
console=console,
|
||||
# put most everyting under a lib/ subfolder
|
||||
contents_directory='lib',
|
||||
disable_windowed_traceback=disable_windowed_traceback,
|
||||
argv_emulation=argv_emulation,
|
||||
target_arch=target_arch,
|
||||
codesign_identity=codesign_identity,
|
||||
entitlements_file=entitlements_file,
|
||||
version=version,
|
||||
)
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
strip=strip,
|
||||
upx=upx,
|
||||
upx_exclude=[],
|
||||
name=name,
|
||||
)
|
||||
else:
|
||||
# Build one file
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name=name,
|
||||
debug=debug,
|
||||
bootloader_ignore_signals=bootloader_ignore_signals,
|
||||
manifest=manifest,
|
||||
strip=strip,
|
||||
upx=upx,
|
||||
console=console,
|
||||
disable_windowed_traceback=disable_windowed_traceback,
|
||||
argv_emulation=argv_emulation,
|
||||
target_arch=target_arch,
|
||||
codesign_identity=codesign_identity,
|
||||
entitlements_file=entitlements_file,
|
||||
version=version,
|
||||
)
|
||||
|
||||
78
src/gam.wxs
78
src/gam.wxs
@@ -1,78 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" >
|
||||
<Product
|
||||
Id="*"
|
||||
Name="GAM7"
|
||||
Language="1033"
|
||||
Version="$(env.GAMVERSION)"
|
||||
Manufacturer="GAM Team - google-apps-manager@googlegroups.com"
|
||||
UpgradeCode="D86B52B2-EFE9-4F9D-8BA3-9D84B9B2D319">
|
||||
<Package
|
||||
InstallerVersion="500" Compressed="yes" InstallScope="perMachine" />
|
||||
|
||||
<MajorUpgrade
|
||||
DowngradeErrorMessage=
|
||||
"A newer version of [ProductName] is already installed."
|
||||
Schedule="afterInstallExecute" />
|
||||
<MediaTemplate EmbedCab="yes" />
|
||||
|
||||
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
|
||||
<WixVariable Id="WixUILicenseRtf" Value="LICENSE.rtf" />
|
||||
<UIRef Id="WixUI_InstallDir" />
|
||||
|
||||
<Feature
|
||||
Id="gam"
|
||||
Title="GAM7"
|
||||
Level="1">
|
||||
<ComponentGroupRef Id="ProductComponents" />
|
||||
</Feature>
|
||||
</Product>
|
||||
|
||||
<Fragment>
|
||||
<SetDirectory Id="WINDOWSVOLUME" Value="[WindowsVolume]"/>
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="WINDOWSVOLUME">
|
||||
<Directory Id="INSTALLFOLDER" Name="GAM7">
|
||||
<Directory Id="lib" Name="lib">
|
||||
</Directory>
|
||||
</Directory>
|
||||
</Directory>
|
||||
</Directory>
|
||||
</Fragment>
|
||||
|
||||
<Fragment>
|
||||
<!-- Group of components that are our main application items -->
|
||||
<ComponentGroup
|
||||
Id="ProductComponents"
|
||||
Directory="INSTALLFOLDER"
|
||||
Source="dist/gam/gam7">
|
||||
<Component Id="gam_exe" Guid="d046ea24-c9f8-40ca-84db-70b0119933ff">
|
||||
<File Name="gam.exe" KeyPath="yes" />
|
||||
<Environment Id="PATH" Name="PATH" Value="[INSTALLFOLDER]" Permanent="yes" Part="last" Action="set" System="yes" />
|
||||
</Component>
|
||||
<Component Id="license" Guid="c76864c5-d005-44d5-bb7c-a27e5923792d">
|
||||
<File Name="LICENSE" KeyPath="yes" />
|
||||
</Component>
|
||||
<Component Id="gam_setup_bat" Guid="5e6bbacb-d86f-4d80-a10b-89b81ee63fcb">
|
||||
<File Name="gam-setup.bat" KeyPath="yes" />
|
||||
</Component>
|
||||
<Component Id="GamCommands_txt" Guid="a2dca862-b222-469e-a637-95ea2a1c53e7">
|
||||
<File Name="GamCommands.txt" KeyPath="yes" />
|
||||
</Component>
|
||||
<Component Id="GamUpdate_txt" Guid="1b7cdd48-0fff-4943-a219-102fcd14c755">
|
||||
<File Name="GamUpdate.txt" KeyPath="yes" />
|
||||
</Component>
|
||||
<Component Id="cacerts_pem" Guid="61fe2b2d-1646-4bed-b844-193965e97727">
|
||||
<File Name="cacerts.pem" KeyPath="yes" />
|
||||
</Component>
|
||||
<ComponentGroupRef Id="Lib" />
|
||||
</ComponentGroup>
|
||||
</Fragment>
|
||||
|
||||
<Fragment>
|
||||
<InstallUISequence>
|
||||
<ExecuteAction />
|
||||
<Show Dialog="WelcomeDlg" Before="ProgressDlg" />
|
||||
</InstallUISequence>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
82685
src/gam/__init__.py
82685
src/gam/__init__.py
File diff suppressed because it is too large
Load Diff
@@ -1,48 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# GAM
|
||||
#
|
||||
# Copyright 2023, All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import multiprocessing
|
||||
import platform
|
||||
import sys
|
||||
|
||||
import gam
|
||||
|
||||
def main():
|
||||
gam.initializeLogging()
|
||||
rc = gam.ProcessGAMCommand(sys.argv)
|
||||
try:
|
||||
sys.stdout.flush()
|
||||
except (IOError, ValueError):
|
||||
pass
|
||||
sys.exit(rc)
|
||||
|
||||
# Run from command line
|
||||
if __name__ == '__main__':
|
||||
if getattr(sys, 'frozen', False): # we're frozen:
|
||||
multiprocessing.freeze_support()
|
||||
#if platform.system() == 'Linux':
|
||||
# set explictly since it's not default in Python < 3.14, forkserver should
|
||||
# be safer than fork and less likely to see bulk command hangs.
|
||||
#multiprocessing.set_start_method('forkserver')
|
||||
#else:
|
||||
|
||||
# Python 3.14.4 and PyInstaller 6.19.0 don't play nice with forkserver
|
||||
# on Linux. For the time being use spawn everywhere.
|
||||
multiprocessing.set_start_method('spawn')
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,41 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2009 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License 2.0;
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
# This module is used for version 2 of the Google Data APIs.
|
||||
|
||||
|
||||
# __author__ = 'j.s@google.com (Jeff Scudder)'
|
||||
|
||||
import base64
|
||||
|
||||
|
||||
class BasicAuth(object):
|
||||
"""Sets the Authorization header as defined in RFC1945"""
|
||||
|
||||
def __init__(self, user_id, password):
|
||||
self.basic_cookie = base64.encodestring(
|
||||
'%s:%s' % (user_id, password)).strip()
|
||||
|
||||
def modify_request(self, http_request):
|
||||
http_request.headers['Authorization'] = 'Basic %s' % self.basic_cookie
|
||||
|
||||
ModifyRequest = modify_request
|
||||
|
||||
|
||||
class NoAuth(object):
|
||||
def modify_request(self, http_request):
|
||||
pass
|
||||
@@ -1,214 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2009 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License 2.0;
|
||||
|
||||
|
||||
|
||||
"""AtomPubClient provides CRUD ops. in line with the Atom Publishing Protocol.
|
||||
|
||||
"""
|
||||
|
||||
# __author__ = 'j.s@google.com (Jeff Scudder)'
|
||||
|
||||
import atom.http_core
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MissingHost(Error):
|
||||
pass
|
||||
|
||||
|
||||
class AtomPubClient(object):
|
||||
host = None
|
||||
auth_token = None
|
||||
ssl = False # Whether to force all requests over https
|
||||
xoauth_requestor_id = None
|
||||
|
||||
def __init__(self, http_client=None, host=None, auth_token=None, source=None,
|
||||
xoauth_requestor_id=None, **kwargs):
|
||||
"""Creates a new AtomPubClient instance.
|
||||
|
||||
Args:
|
||||
source: The name of your application.
|
||||
http_client: An object capable of performing HTTP requests through a
|
||||
request method. This object is used to perform the request
|
||||
when the AtomPubClient's request method is called. Used to
|
||||
allow HTTP requests to be directed to a mock server, or use
|
||||
an alternate library instead of the default of httplib to
|
||||
make HTTP requests.
|
||||
host: str The default host name to use if a host is not specified in the
|
||||
requested URI.
|
||||
auth_token: An object which sets the HTTP Authorization header when its
|
||||
modify_request method is called.
|
||||
"""
|
||||
self.http_client = http_client or atom.http_core.ProxiedHttpClient()
|
||||
if host is not None:
|
||||
self.host = host
|
||||
if auth_token is not None:
|
||||
self.auth_token = auth_token
|
||||
self.xoauth_requestor_id = xoauth_requestor_id
|
||||
self.source = source
|
||||
|
||||
def request(self, method=None, uri=None, auth_token=None,
|
||||
http_request=None, **kwargs):
|
||||
"""Performs an HTTP request to the server indicated.
|
||||
|
||||
Uses the http_client instance to make the request.
|
||||
|
||||
Args:
|
||||
method: The HTTP method as a string, usually one of 'GET', 'POST',
|
||||
'PUT', or 'DELETE'
|
||||
uri: The URI desired as a string or atom.http_core.Uri.
|
||||
http_request:
|
||||
auth_token: An authorization token object whose modify_request method
|
||||
sets the HTTP Authorization header.
|
||||
|
||||
Returns:
|
||||
The results of calling self.http_client.request. With the default
|
||||
http_client, this is an HTTP response object.
|
||||
"""
|
||||
# Modify the request based on the AtomPubClient settings and parameters
|
||||
# passed in to the request.
|
||||
http_request = self.modify_request(http_request)
|
||||
if isinstance(uri, str):
|
||||
uri = atom.http_core.Uri.parse_uri(uri)
|
||||
if uri is not None:
|
||||
uri.modify_request(http_request)
|
||||
if isinstance(method, str):
|
||||
http_request.method = method
|
||||
# Any unrecognized arguments are assumed to be capable of modifying the
|
||||
# HTTP request.
|
||||
for name, value in kwargs.items():
|
||||
if value is not None:
|
||||
if hasattr(value, 'modify_request'):
|
||||
value.modify_request(http_request)
|
||||
else:
|
||||
http_request.uri.query[name] = str(value)
|
||||
# Default to an http request if the protocol scheme is not set.
|
||||
if http_request.uri.scheme is None:
|
||||
http_request.uri.scheme = 'http'
|
||||
# Override scheme. Force requests over https.
|
||||
if self.ssl:
|
||||
http_request.uri.scheme = 'https'
|
||||
if http_request.uri.path is None:
|
||||
http_request.uri.path = '/'
|
||||
# Add the Authorization header at the very end. The Authorization header
|
||||
# value may need to be calculated using information in the request.
|
||||
if auth_token:
|
||||
auth_token.modify_request(http_request)
|
||||
elif self.auth_token:
|
||||
self.auth_token.modify_request(http_request)
|
||||
# Check to make sure there is a host in the http_request.
|
||||
if http_request.uri.host is None:
|
||||
raise MissingHost('No host provided in request %s %s' % (
|
||||
http_request.method, str(http_request.uri)))
|
||||
# Perform the fully specified request using the http_client instance.
|
||||
# Sends the request to the server and returns the server's response.
|
||||
return self.http_client.request(http_request)
|
||||
|
||||
Request = request
|
||||
|
||||
def get(self, uri=None, auth_token=None, http_request=None, **kwargs):
|
||||
"""Performs a request using the GET method, returns an HTTP response."""
|
||||
return self.request(method='GET', uri=uri, auth_token=auth_token,
|
||||
http_request=http_request, **kwargs)
|
||||
|
||||
Get = get
|
||||
|
||||
def post(self, uri=None, data=None, auth_token=None, http_request=None,
|
||||
**kwargs):
|
||||
"""Sends data using the POST method, returns an HTTP response."""
|
||||
return self.request(method='POST', uri=uri, auth_token=auth_token,
|
||||
http_request=http_request, data=data, **kwargs)
|
||||
|
||||
Post = post
|
||||
|
||||
def put(self, uri=None, data=None, auth_token=None, http_request=None,
|
||||
**kwargs):
|
||||
"""Sends data using the PUT method, returns an HTTP response."""
|
||||
return self.request(method='PUT', uri=uri, auth_token=auth_token,
|
||||
http_request=http_request, data=data, **kwargs)
|
||||
|
||||
Put = put
|
||||
|
||||
def delete(self, uri=None, auth_token=None, http_request=None, **kwargs):
|
||||
"""Performs a request using the DELETE method, returns an HTTP response."""
|
||||
return self.request(method='DELETE', uri=uri, auth_token=auth_token,
|
||||
http_request=http_request, **kwargs)
|
||||
|
||||
Delete = delete
|
||||
|
||||
def modify_request(self, http_request):
|
||||
"""Changes the HTTP request before sending it to the server.
|
||||
|
||||
Sets the User-Agent HTTP header and fills in the HTTP host portion
|
||||
of the URL if one was not included in the request (for this it uses
|
||||
the self.host member if one is set). This method is called in
|
||||
self.request.
|
||||
|
||||
Args:
|
||||
http_request: An atom.http_core.HttpRequest() (optional) If one is
|
||||
not provided, a new HttpRequest is instantiated.
|
||||
|
||||
Returns:
|
||||
An atom.http_core.HttpRequest() with the User-Agent header set and
|
||||
if this client has a value in its host member, the host in the request
|
||||
URL is set.
|
||||
"""
|
||||
if http_request is None:
|
||||
http_request = atom.http_core.HttpRequest()
|
||||
|
||||
if self.host is not None and http_request.uri.host is None:
|
||||
http_request.uri.host = self.host
|
||||
|
||||
if self.xoauth_requestor_id is not None:
|
||||
http_request.uri.query['xoauth_requestor_id'] = self.xoauth_requestor_id
|
||||
|
||||
# Set the user agent header for logging purposes.
|
||||
if self.source:
|
||||
http_request.headers['User-Agent'] = '%s gdata-py/2.0.18' % self.source
|
||||
else:
|
||||
http_request.headers['User-Agent'] = 'gdata-py/2.0.17'
|
||||
|
||||
return http_request
|
||||
|
||||
ModifyRequest = modify_request
|
||||
|
||||
|
||||
class CustomHeaders(object):
|
||||
"""Add custom headers to an http_request.
|
||||
|
||||
Usage:
|
||||
>>> custom_headers = atom.client.CustomHeaders(header1='value1',
|
||||
header2='value2')
|
||||
>>> client.get(uri, custom_headers=custom_headers)
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Creates a CustomHeaders instance.
|
||||
|
||||
Initialize the headers dictionary with the arguments list.
|
||||
"""
|
||||
self.headers = kwargs
|
||||
|
||||
def modify_request(self, http_request):
|
||||
"""Changes the HTTP request before sending it to the server.
|
||||
|
||||
Adds the custom headers to the HTTP request.
|
||||
|
||||
Args:
|
||||
http_request: An atom.http_core.HttpRequest().
|
||||
|
||||
Returns:
|
||||
An atom.http_core.HttpRequest() with the added custom headers.
|
||||
"""
|
||||
|
||||
for name, value in self.headers.items():
|
||||
if value is not None:
|
||||
http_request.headers[name] = value
|
||||
return http_request
|
||||
@@ -1,535 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2008 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License 2.0;
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
# This module is used for version 2 of the Google Data APIs.
|
||||
|
||||
|
||||
# __author__ = 'j.s@google.com (Jeff Scudder)'
|
||||
|
||||
import inspect
|
||||
|
||||
import lxml.etree as ElementTree
|
||||
|
||||
try:
|
||||
from xml.dom.minidom import parseString as xmlString
|
||||
except ImportError:
|
||||
xmlString = None
|
||||
|
||||
STRING_ENCODING = 'utf-8'
|
||||
|
||||
|
||||
class XmlElement(object):
|
||||
"""Represents an element node in an XML document.
|
||||
|
||||
The text member is a UTF-8 encoded str or unicode.
|
||||
"""
|
||||
_qname = None
|
||||
_other_elements = None
|
||||
_other_attributes = None
|
||||
# The rule set contains mappings for XML qnames to child members and the
|
||||
# appropriate member classes.
|
||||
_rule_set = None
|
||||
_members = None
|
||||
text = None
|
||||
|
||||
def __init__(self, text=None, *args, **kwargs):
|
||||
if ('_members' not in self.__class__.__dict__
|
||||
or self.__class__._members is None):
|
||||
self.__class__._members = tuple(self.__class__._list_xml_members())
|
||||
for member_name, member_type in self.__class__._members:
|
||||
if member_name in kwargs:
|
||||
setattr(self, member_name, kwargs[member_name])
|
||||
else:
|
||||
if isinstance(member_type, list):
|
||||
setattr(self, member_name, [])
|
||||
else:
|
||||
setattr(self, member_name, None)
|
||||
self._other_elements = []
|
||||
self._other_attributes = {}
|
||||
if text is not None:
|
||||
self.text = text
|
||||
|
||||
def _list_xml_members(cls):
|
||||
"""Generator listing all members which are XML elements or attributes.
|
||||
|
||||
The following members would be considered XML members:
|
||||
foo = 'abc' - indicates an XML attribute with the qname abc
|
||||
foo = SomeElement - indicates an XML child element
|
||||
foo = [AnElement] - indicates a repeating XML child element, each instance
|
||||
will be stored in a list in this member
|
||||
foo = ('att1', '{http://example.com/namespace}att2') - indicates an XML
|
||||
attribute which has different parsing rules in different versions of
|
||||
the protocol. Version 1 of the XML parsing rules will look for an
|
||||
attribute with the qname 'att1' but verion 2 of the parsing rules will
|
||||
look for a namespaced attribute with the local name of 'att2' and an
|
||||
XML namespace of 'http://example.com/namespace'.
|
||||
"""
|
||||
members = []
|
||||
for pair in inspect.getmembers(cls):
|
||||
if not pair[0].startswith('_') and pair[0] != 'text':
|
||||
member_type = pair[1]
|
||||
if (isinstance(member_type, tuple) or isinstance(member_type, list)
|
||||
or isinstance(member_type, str)
|
||||
or (inspect.isclass(member_type)
|
||||
and issubclass(member_type, XmlElement))):
|
||||
members.append(pair)
|
||||
return members
|
||||
|
||||
_list_xml_members = classmethod(_list_xml_members)
|
||||
|
||||
def _get_rules(cls, version):
|
||||
"""Initializes the _rule_set for the class which is used when parsing XML.
|
||||
|
||||
This method is used internally for parsing and generating XML for an
|
||||
XmlElement. It is not recommended that you call this method directly.
|
||||
|
||||
Returns:
|
||||
A tuple containing the XML parsing rules for the appropriate version.
|
||||
|
||||
The tuple looks like:
|
||||
(qname, {sub_element_qname: (member_name, member_class, repeating), ..},
|
||||
{attribute_qname: member_name})
|
||||
|
||||
To give a couple of concrete example, the atom.data.Control _get_rules
|
||||
with version of 2 will return:
|
||||
('{http://www.w3.org/2007/app}control',
|
||||
{'{http://www.w3.org/2007/app}draft': ('draft',
|
||||
<class 'atom.data.Draft'>,
|
||||
False)},
|
||||
{})
|
||||
Calling _get_rules with version 1 on gdata.data.FeedLink will produce:
|
||||
('{http://schemas.google.com/g/2005}feedLink',
|
||||
{'{http://www.w3.org/2005/Atom}feed': ('feed',
|
||||
<class 'gdata.data.GDFeed'>,
|
||||
False)},
|
||||
{'href': 'href', 'readOnly': 'read_only', 'countHint': 'count_hint',
|
||||
'rel': 'rel'})
|
||||
"""
|
||||
# Initialize the _rule_set to make sure there is a slot available to store
|
||||
# the parsing rules for this version of the XML schema.
|
||||
# Look for rule set in the class __dict__ proxy so that only the
|
||||
# _rule_set for this class will be found. By using the dict proxy
|
||||
# we avoid finding rule_sets defined in superclasses.
|
||||
# The four lines below provide support for any number of versions, but it
|
||||
# runs a bit slower then hard coding slots for two versions, so I'm using
|
||||
# the below two lines.
|
||||
# if '_rule_set' not in cls.__dict__ or cls._rule_set is None:
|
||||
# cls._rule_set = []
|
||||
# while len(cls.__dict__['_rule_set']) < version:
|
||||
# cls._rule_set.append(None)
|
||||
# If there is no rule set cache in the class, provide slots for two XML
|
||||
# versions. If and when there is a version 3, this list will need to be
|
||||
# expanded.
|
||||
if '_rule_set' not in cls.__dict__ or cls._rule_set is None:
|
||||
cls._rule_set = [None, None]
|
||||
# If a version higher than 2 is requested, fall back to version 2 because
|
||||
# 2 is currently the highest supported version.
|
||||
if version > 2:
|
||||
return cls._get_rules(2)
|
||||
# Check the dict proxy for the rule set to avoid finding any rule sets
|
||||
# which belong to the superclass. We only want rule sets for this class.
|
||||
if cls._rule_set[version - 1] is None:
|
||||
# The rule set for each version consists of the qname for this element
|
||||
# ('{namespace}tag'), a dictionary (elements) for looking up the
|
||||
# corresponding class member when given a child element's qname, and a
|
||||
# dictionary (attributes) for looking up the corresponding class member
|
||||
# when given an XML attribute's qname.
|
||||
elements = {}
|
||||
attributes = {}
|
||||
if ('_members' not in cls.__dict__ or cls._members is None):
|
||||
cls._members = tuple(cls._list_xml_members())
|
||||
for member_name, target in cls._members:
|
||||
if isinstance(target, list):
|
||||
# This member points to a repeating element.
|
||||
elements[_get_qname(target[0], version)] = (member_name, target[0],
|
||||
True)
|
||||
elif isinstance(target, tuple):
|
||||
# This member points to a versioned XML attribute.
|
||||
if version <= len(target):
|
||||
attributes[target[version - 1]] = member_name
|
||||
else:
|
||||
attributes[target[-1]] = member_name
|
||||
elif isinstance(target, str):
|
||||
# This member points to an XML attribute.
|
||||
attributes[target] = member_name
|
||||
elif issubclass(target, XmlElement):
|
||||
# This member points to a single occurance element.
|
||||
elements[_get_qname(target, version)] = (member_name, target, False)
|
||||
version_rules = (_get_qname(cls, version), elements, attributes)
|
||||
cls._rule_set[version - 1] = version_rules
|
||||
return version_rules
|
||||
else:
|
||||
return cls._rule_set[version - 1]
|
||||
|
||||
_get_rules = classmethod(_get_rules)
|
||||
|
||||
def get_elements(self, tag=None, namespace=None, version=1):
|
||||
"""Find all sub elements which match the tag and namespace.
|
||||
|
||||
To find all elements in this object, call get_elements with the tag and
|
||||
namespace both set to None (the default). This method searches through
|
||||
the object's members and the elements stored in _other_elements which
|
||||
did not match any of the XML parsing rules for this class.
|
||||
|
||||
Args:
|
||||
tag: str
|
||||
namespace: str
|
||||
version: int Specifies the version of the XML rules to be used when
|
||||
searching for matching elements.
|
||||
|
||||
Returns:
|
||||
A list of the matching XmlElements.
|
||||
"""
|
||||
matches = []
|
||||
ignored1, elements, ignored2 = self.__class__._get_rules(version)
|
||||
if elements:
|
||||
for qname, element_def in elements.items():
|
||||
member = getattr(self, element_def[0])
|
||||
if member:
|
||||
if _qname_matches(tag, namespace, qname):
|
||||
if element_def[2]:
|
||||
# If this is a repeating element, copy all instances into the
|
||||
# result list.
|
||||
matches.extend(member)
|
||||
else:
|
||||
matches.append(member)
|
||||
for element in self._other_elements:
|
||||
if _qname_matches(tag, namespace, element._qname):
|
||||
matches.append(element)
|
||||
return matches
|
||||
|
||||
GetElements = get_elements
|
||||
# FindExtensions and FindChildren are provided for backwards compatibility
|
||||
# to the atom.AtomBase class.
|
||||
# However, FindExtensions may return more results than the v1 atom.AtomBase
|
||||
# method does, because get_elements searches both the expected children
|
||||
# and the unexpected "other elements". The old AtomBase.FindExtensions
|
||||
# method searched only "other elements" AKA extension_elements.
|
||||
FindExtensions = get_elements
|
||||
FindChildren = get_elements
|
||||
|
||||
def get_attributes(self, tag=None, namespace=None, version=1):
|
||||
"""Find all attributes which match the tag and namespace.
|
||||
|
||||
To find all attributes in this object, call get_attributes with the tag
|
||||
and namespace both set to None (the default). This method searches
|
||||
through the object's members and the attributes stored in
|
||||
_other_attributes which did not fit any of the XML parsing rules for this
|
||||
class.
|
||||
|
||||
Args:
|
||||
tag: str
|
||||
namespace: str
|
||||
version: int Specifies the version of the XML rules to be used when
|
||||
searching for matching attributes.
|
||||
|
||||
Returns:
|
||||
A list of XmlAttribute objects for the matching attributes.
|
||||
"""
|
||||
matches = []
|
||||
ignored1, ignored2, attributes = self.__class__._get_rules(version)
|
||||
if attributes:
|
||||
for qname, attribute_def in attributes.items():
|
||||
if isinstance(attribute_def, (list, tuple)):
|
||||
attribute_def = attribute_def[0]
|
||||
member = getattr(self, attribute_def)
|
||||
# TODO: ensure this hasn't broken existing behavior.
|
||||
# member = getattr(self, attribute_def[0])
|
||||
if member:
|
||||
if _qname_matches(tag, namespace, qname):
|
||||
matches.append(XmlAttribute(qname, member))
|
||||
for qname, value in self._other_attributes.items():
|
||||
if _qname_matches(tag, namespace, qname):
|
||||
matches.append(XmlAttribute(qname, value))
|
||||
return matches
|
||||
|
||||
GetAttributes = get_attributes
|
||||
|
||||
def _harvest_tree(self, tree, version=1):
|
||||
"""Populates object members from the data in the tree Element."""
|
||||
qname, elements, attributes = self.__class__._get_rules(version)
|
||||
for element in tree:
|
||||
if elements and element.tag in elements:
|
||||
definition = elements[element.tag]
|
||||
# If this is a repeating element, make sure the member is set to a
|
||||
# list.
|
||||
if definition[2]:
|
||||
if getattr(self, definition[0]) is None:
|
||||
setattr(self, definition[0], [])
|
||||
getattr(self, definition[0]).append(_xml_element_from_tree(element,
|
||||
definition[1], version))
|
||||
else:
|
||||
setattr(self, definition[0], _xml_element_from_tree(element,
|
||||
definition[1], version))
|
||||
else:
|
||||
self._other_elements.append(_xml_element_from_tree(element, XmlElement,
|
||||
version))
|
||||
for attrib, value in tree.attrib.items():
|
||||
if attributes and attrib in attributes:
|
||||
setattr(self, attributes[attrib], value)
|
||||
else:
|
||||
self._other_attributes[attrib] = value
|
||||
if tree.text:
|
||||
self.text = tree.text
|
||||
|
||||
def _to_tree(self, version=1):
|
||||
new_tree = ElementTree.Element(_get_qname(self, version))
|
||||
self._attach_members(new_tree, version)
|
||||
return new_tree
|
||||
|
||||
def _attach_members(self, tree, version=1):
|
||||
"""Convert members to XML elements/attributes and add them to the tree.
|
||||
|
||||
Args:
|
||||
tree: An ElementTree.Element which will be modified. The members of
|
||||
this object will be added as child elements or attributes
|
||||
according to the rules described in _expected_elements and
|
||||
_expected_attributes. The elements and attributes stored in
|
||||
other_attributes and other_elements are also added a children
|
||||
of this tree.
|
||||
version: int Ingnored in this method but used by VersionedElement.
|
||||
encoding: str (optional)
|
||||
"""
|
||||
qname, elements, attributes = self.__class__._get_rules(version)
|
||||
encoding = STRING_ENCODING
|
||||
# Add the expected elements and attributes to the tree.
|
||||
if elements:
|
||||
for tag, element_def in elements.items():
|
||||
member = getattr(self, element_def[0])
|
||||
# If this is a repeating element and there are members in the list.
|
||||
if member and element_def[2]:
|
||||
for instance in member:
|
||||
instance._become_child(tree, version)
|
||||
elif member:
|
||||
member._become_child(tree, version)
|
||||
if attributes:
|
||||
for attribute_tag, member_name in attributes.items():
|
||||
value = getattr(self, member_name)
|
||||
if value:
|
||||
tree.attrib[attribute_tag] = value
|
||||
# Add the unexpected (other) elements and attributes to the tree.
|
||||
for element in self._other_elements:
|
||||
element._become_child(tree, version)
|
||||
for key, value in self._other_attributes.items():
|
||||
# I'm not sure if unicode can be used in the attribute name, so for now
|
||||
# we assume the encoding is correct for the attribute name.
|
||||
if not isinstance(value, str):
|
||||
value = value.decode(encoding)
|
||||
tree.attrib[key] = value
|
||||
if self.text:
|
||||
if isinstance(self.text, str):
|
||||
tree.text = self.text
|
||||
else:
|
||||
tree.text = self.text.decode(encoding)
|
||||
|
||||
def to_string(self, version=1, encoding=None, pretty_print=None):
|
||||
"""Converts this object to XML."""
|
||||
|
||||
tree_string = ElementTree.tostring(self._to_tree(version))
|
||||
|
||||
if pretty_print and xmlString is not None:
|
||||
return xmlString(tree_string).toprettyxml()
|
||||
|
||||
return tree_string
|
||||
|
||||
ToString = to_string
|
||||
|
||||
def __str__(self):
|
||||
return self.to_string()
|
||||
|
||||
def _become_child(self, tree, version=1):
|
||||
"""Adds a child element to tree with the XML data in self."""
|
||||
new_child = ElementTree.Element(_get_qname(self, version))
|
||||
tree.append(new_child)
|
||||
new_child.tag = _get_qname(self, version)
|
||||
self._attach_members(new_child, version)
|
||||
|
||||
def __get_extension_elements(self):
|
||||
return self._other_elements
|
||||
|
||||
def __set_extension_elements(self, elements):
|
||||
self._other_elements = elements
|
||||
|
||||
extension_elements = property(__get_extension_elements,
|
||||
__set_extension_elements,
|
||||
"""Provides backwards compatibility for v1 atom.AtomBase classes.""")
|
||||
|
||||
def __get_extension_attributes(self):
|
||||
return self._other_attributes
|
||||
|
||||
def __set_extension_attributes(self, attributes):
|
||||
self._other_attributes = attributes
|
||||
|
||||
extension_attributes = property(__get_extension_attributes,
|
||||
__set_extension_attributes,
|
||||
"""Provides backwards compatibility for v1 atom.AtomBase classes.""")
|
||||
|
||||
def _get_tag(self, version=1):
|
||||
qname = _get_qname(self, version)
|
||||
if qname:
|
||||
return qname[qname.find('}') + 1:]
|
||||
return None
|
||||
|
||||
def _get_namespace(self, version=1):
|
||||
qname = _get_qname(self, version)
|
||||
if qname.startswith('{'):
|
||||
return qname[1:qname.find('}')]
|
||||
else:
|
||||
return None
|
||||
|
||||
def _set_tag(self, tag):
|
||||
if isinstance(self._qname, tuple):
|
||||
self._qname = self._qname.copy()
|
||||
if self._qname[0].startswith('{'):
|
||||
self._qname[0] = '{%s}%s' % (self._get_namespace(1), tag)
|
||||
else:
|
||||
self._qname[0] = tag
|
||||
else:
|
||||
if self._qname is not None and self._qname.startswith('{'):
|
||||
self._qname = '{%s}%s' % (self._get_namespace(), tag)
|
||||
else:
|
||||
self._qname = tag
|
||||
|
||||
def _set_namespace(self, namespace):
|
||||
tag = self._get_tag(1)
|
||||
if tag is None:
|
||||
tag = ''
|
||||
if isinstance(self._qname, tuple):
|
||||
self._qname = self._qname.copy()
|
||||
if namespace:
|
||||
self._qname[0] = '{%s}%s' % (namespace, tag)
|
||||
else:
|
||||
self._qname[0] = tag
|
||||
else:
|
||||
if namespace:
|
||||
self._qname = '{%s}%s' % (namespace, tag)
|
||||
else:
|
||||
self._qname = tag
|
||||
|
||||
tag = property(_get_tag, _set_tag,
|
||||
"""Provides backwards compatibility for v1 atom.AtomBase classes.""")
|
||||
|
||||
namespace = property(_get_namespace, _set_namespace,
|
||||
"""Provides backwards compatibility for v1 atom.AtomBase classes.""")
|
||||
|
||||
# Provided for backwards compatibility to atom.ExtensionElement
|
||||
children = extension_elements
|
||||
attributes = extension_attributes
|
||||
|
||||
|
||||
def _get_qname(element, version):
|
||||
if isinstance(element._qname, tuple):
|
||||
if version <= len(element._qname):
|
||||
return element._qname[version - 1]
|
||||
else:
|
||||
return element._qname[-1]
|
||||
else:
|
||||
return element._qname
|
||||
|
||||
|
||||
def _qname_matches(tag, namespace, qname):
|
||||
"""Logic determines if a QName matches the desired local tag and namespace.
|
||||
|
||||
This is used in XmlElement.get_elements and XmlElement.get_attributes to
|
||||
find matches in the element's members (among all expected-and-unexpected
|
||||
elements-and-attributes).
|
||||
|
||||
Args:
|
||||
expected_tag: string
|
||||
expected_namespace: string
|
||||
qname: string in the form '{xml_namespace}localtag' or 'tag' if there is
|
||||
no namespace.
|
||||
|
||||
Returns:
|
||||
boolean True if the member's tag and namespace fit the expected tag and
|
||||
namespace.
|
||||
"""
|
||||
# If there is no expected namespace or tag, then everything will match.
|
||||
if qname is None:
|
||||
member_tag = None
|
||||
member_namespace = None
|
||||
else:
|
||||
if qname.startswith('{'):
|
||||
member_namespace = qname[1:qname.index('}')]
|
||||
member_tag = qname[qname.index('}') + 1:]
|
||||
else:
|
||||
member_namespace = None
|
||||
member_tag = qname
|
||||
return ((tag is None and namespace is None)
|
||||
# If there is a tag, but no namespace, see if the local tag matches.
|
||||
or (namespace is None and member_tag == tag)
|
||||
# There was no tag, but there was a namespace so see if the namespaces
|
||||
# match.
|
||||
or (tag is None and member_namespace == namespace)
|
||||
# There was no tag, and the desired elements have no namespace, so check
|
||||
# to see that the member's namespace is None.
|
||||
or (tag is None and namespace == ''
|
||||
and member_namespace is None)
|
||||
# The tag and the namespace both match.
|
||||
or (tag == member_tag
|
||||
and namespace == member_namespace)
|
||||
# The tag matches, and the expected namespace is the empty namespace,
|
||||
# check to make sure the member's namespace is None.
|
||||
or (tag == member_tag and namespace == ''
|
||||
and member_namespace is None))
|
||||
|
||||
|
||||
def parse(xml_string, target_class=None, version=1):
|
||||
"""Parses the XML string according to the rules for the target_class.
|
||||
|
||||
Args:
|
||||
xml_string: bytes
|
||||
target_class: XmlElement or a subclass. If None is specified, the
|
||||
XmlElement class is used.
|
||||
version: int (optional) The version of the schema which should be used when
|
||||
converting the XML into an object. The default is 1.
|
||||
encoding: str (optional) The character encoding of the bytes in the
|
||||
xml_string. Default is 'UTF-8'.
|
||||
"""
|
||||
if target_class is None:
|
||||
target_class = XmlElement
|
||||
if not isinstance(xml_string, bytes):
|
||||
raise Exception("This function only accepts bytes")
|
||||
tree = ElementTree.fromstring(xml_string)
|
||||
return _xml_element_from_tree(tree, target_class, version)
|
||||
|
||||
|
||||
Parse = parse
|
||||
xml_element_from_string = parse
|
||||
XmlElementFromString = xml_element_from_string
|
||||
|
||||
|
||||
def _xml_element_from_tree(tree, target_class, version=1):
|
||||
if target_class._qname is None:
|
||||
instance = target_class()
|
||||
instance._qname = tree.tag
|
||||
instance._harvest_tree(tree, version)
|
||||
return instance
|
||||
# TODO handle the namespace-only case
|
||||
# Namespace only will be used with Google Spreadsheets rows and
|
||||
# Google Base item attributes.
|
||||
elif tree.tag == _get_qname(target_class, version):
|
||||
instance = target_class()
|
||||
instance._harvest_tree(tree, version)
|
||||
return instance
|
||||
return None
|
||||
|
||||
|
||||
class XmlAttribute(object):
|
||||
def __init__(self, qname, value):
|
||||
self._qname = qname
|
||||
self.value = value
|
||||
@@ -1,327 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2009 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License 2.0;
|
||||
|
||||
|
||||
|
||||
# This module is used for version 2 of the Google Data APIs.
|
||||
|
||||
|
||||
# __author__ = 'j.s@google.com (Jeff Scudder)'
|
||||
|
||||
import atom.core
|
||||
|
||||
XML_TEMPLATE = '{http://www.w3.org/XML/1998/namespace}%s'
|
||||
ATOM_TEMPLATE = '{http://www.w3.org/2005/Atom}%s'
|
||||
APP_TEMPLATE_V1 = '{http://purl.org/atom/app#}%s'
|
||||
APP_TEMPLATE_V2 = '{http://www.w3.org/2007/app}%s'
|
||||
|
||||
|
||||
class Name(atom.core.XmlElement):
|
||||
"""The atom:name element."""
|
||||
_qname = ATOM_TEMPLATE % 'name'
|
||||
|
||||
|
||||
class Email(atom.core.XmlElement):
|
||||
"""The atom:email element."""
|
||||
_qname = ATOM_TEMPLATE % 'email'
|
||||
|
||||
|
||||
class Uri(atom.core.XmlElement):
|
||||
"""The atom:uri element."""
|
||||
_qname = ATOM_TEMPLATE % 'uri'
|
||||
|
||||
|
||||
class Person(atom.core.XmlElement):
|
||||
"""A foundation class which atom:author and atom:contributor extend.
|
||||
|
||||
A person contains information like name, email address, and web page URI for
|
||||
an author or contributor to an Atom feed.
|
||||
"""
|
||||
name = Name
|
||||
email = Email
|
||||
uri = Uri
|
||||
|
||||
|
||||
class Author(Person):
|
||||
"""The atom:author element.
|
||||
|
||||
An author is a required element in Feed unless each Entry contains an Author.
|
||||
"""
|
||||
_qname = ATOM_TEMPLATE % 'author'
|
||||
|
||||
|
||||
class Contributor(Person):
|
||||
"""The atom:contributor element."""
|
||||
_qname = ATOM_TEMPLATE % 'contributor'
|
||||
|
||||
|
||||
class Link(atom.core.XmlElement):
|
||||
"""The atom:link element."""
|
||||
_qname = ATOM_TEMPLATE % 'link'
|
||||
href = 'href'
|
||||
rel = 'rel'
|
||||
type = 'type'
|
||||
hreflang = 'hreflang'
|
||||
title = 'title'
|
||||
length = 'length'
|
||||
|
||||
|
||||
class Generator(atom.core.XmlElement):
|
||||
"""The atom:generator element."""
|
||||
_qname = ATOM_TEMPLATE % 'generator'
|
||||
uri = 'uri'
|
||||
version = 'version'
|
||||
|
||||
|
||||
class Text(atom.core.XmlElement):
|
||||
"""A foundation class from which atom:title, summary, etc. extend.
|
||||
|
||||
This class should never be instantiated.
|
||||
"""
|
||||
type = 'type'
|
||||
|
||||
|
||||
class Title(Text):
|
||||
"""The atom:title element."""
|
||||
_qname = ATOM_TEMPLATE % 'title'
|
||||
|
||||
|
||||
class Subtitle(Text):
|
||||
"""The atom:subtitle element."""
|
||||
_qname = ATOM_TEMPLATE % 'subtitle'
|
||||
|
||||
|
||||
class Rights(Text):
|
||||
"""The atom:rights element."""
|
||||
_qname = ATOM_TEMPLATE % 'rights'
|
||||
|
||||
|
||||
class Summary(Text):
|
||||
"""The atom:summary element."""
|
||||
_qname = ATOM_TEMPLATE % 'summary'
|
||||
|
||||
|
||||
class Content(Text):
|
||||
"""The atom:content element."""
|
||||
_qname = ATOM_TEMPLATE % 'content'
|
||||
src = 'src'
|
||||
|
||||
|
||||
class Category(atom.core.XmlElement):
|
||||
"""The atom:category element."""
|
||||
_qname = ATOM_TEMPLATE % 'category'
|
||||
term = 'term'
|
||||
scheme = 'scheme'
|
||||
label = 'label'
|
||||
|
||||
|
||||
class Id(atom.core.XmlElement):
|
||||
"""The atom:id element."""
|
||||
_qname = ATOM_TEMPLATE % 'id'
|
||||
|
||||
|
||||
class Icon(atom.core.XmlElement):
|
||||
"""The atom:icon element."""
|
||||
_qname = ATOM_TEMPLATE % 'icon'
|
||||
|
||||
|
||||
class Logo(atom.core.XmlElement):
|
||||
"""The atom:logo element."""
|
||||
_qname = ATOM_TEMPLATE % 'logo'
|
||||
|
||||
|
||||
class Draft(atom.core.XmlElement):
|
||||
"""The app:draft element which indicates if this entry should be public."""
|
||||
_qname = (APP_TEMPLATE_V1 % 'draft', APP_TEMPLATE_V2 % 'draft')
|
||||
|
||||
|
||||
class Control(atom.core.XmlElement):
|
||||
"""The app:control element indicating restrictions on publication.
|
||||
|
||||
The APP control element may contain a draft element indicating whether or
|
||||
not this entry should be publicly available.
|
||||
"""
|
||||
_qname = (APP_TEMPLATE_V1 % 'control', APP_TEMPLATE_V2 % 'control')
|
||||
draft = Draft
|
||||
|
||||
|
||||
class Date(atom.core.XmlElement):
|
||||
"""A parent class for atom:updated, published, etc."""
|
||||
|
||||
|
||||
class Updated(Date):
|
||||
"""The atom:updated element."""
|
||||
_qname = ATOM_TEMPLATE % 'updated'
|
||||
|
||||
|
||||
class Published(Date):
|
||||
"""The atom:published element."""
|
||||
_qname = ATOM_TEMPLATE % 'published'
|
||||
|
||||
|
||||
class LinkFinder(object):
|
||||
"""An "interface" providing methods to find link elements
|
||||
|
||||
Entry elements often contain multiple links which differ in the rel
|
||||
attribute or content type. Often, developers are interested in a specific
|
||||
type of link so this class provides methods to find specific classes of
|
||||
links.
|
||||
|
||||
This class is used as a mixin in Atom entries and feeds.
|
||||
"""
|
||||
|
||||
def find_url(self, rel):
|
||||
"""Returns the URL (as a string) in a link with the desired rel value."""
|
||||
for link in self.link:
|
||||
if link.rel == rel and link.href:
|
||||
return link.href
|
||||
return None
|
||||
|
||||
FindUrl = find_url
|
||||
|
||||
def get_link(self, rel):
|
||||
"""Returns a link object which has the desired rel value.
|
||||
|
||||
If you are interested in the URL instead of the link object,
|
||||
consider using find_url instead.
|
||||
"""
|
||||
for link in self.link:
|
||||
if link.rel == rel and link.href:
|
||||
return link
|
||||
return None
|
||||
|
||||
GetLink = get_link
|
||||
|
||||
def find_self_link(self):
|
||||
"""Find the first link with rel set to 'self'
|
||||
|
||||
Returns:
|
||||
A str containing the link's href or None if none of the links had rel
|
||||
equal to 'self'
|
||||
"""
|
||||
return self.find_url('self')
|
||||
|
||||
FindSelfLink = find_self_link
|
||||
|
||||
def get_self_link(self):
|
||||
return self.get_link('self')
|
||||
|
||||
GetSelfLink = get_self_link
|
||||
|
||||
def find_edit_link(self):
|
||||
return self.find_url('edit')
|
||||
|
||||
FindEditLink = find_edit_link
|
||||
|
||||
def get_edit_link(self):
|
||||
return self.get_link('edit')
|
||||
|
||||
GetEditLink = get_edit_link
|
||||
|
||||
def find_edit_media_link(self):
|
||||
link = self.find_url('edit-media')
|
||||
# Search for media-edit as well since Picasa API used media-edit instead.
|
||||
if link is None:
|
||||
return self.find_url('media-edit')
|
||||
return link
|
||||
|
||||
FindEditMediaLink = find_edit_media_link
|
||||
|
||||
def get_edit_media_link(self):
|
||||
link = self.get_link('edit-media')
|
||||
if link is None:
|
||||
return self.get_link('media-edit')
|
||||
return link
|
||||
|
||||
GetEditMediaLink = get_edit_media_link
|
||||
|
||||
def find_next_link(self):
|
||||
return self.find_url('next')
|
||||
|
||||
FindNextLink = find_next_link
|
||||
|
||||
def get_next_link(self):
|
||||
return self.get_link('next')
|
||||
|
||||
GetNextLink = get_next_link
|
||||
|
||||
def find_license_link(self):
|
||||
return self.find_url('license')
|
||||
|
||||
FindLicenseLink = find_license_link
|
||||
|
||||
def get_license_link(self):
|
||||
return self.get_link('license')
|
||||
|
||||
GetLicenseLink = get_license_link
|
||||
|
||||
def find_alternate_link(self):
|
||||
return self.find_url('alternate')
|
||||
|
||||
FindAlternateLink = find_alternate_link
|
||||
|
||||
def get_alternate_link(self):
|
||||
return self.get_link('alternate')
|
||||
|
||||
GetAlternateLink = get_alternate_link
|
||||
|
||||
|
||||
class FeedEntryParent(atom.core.XmlElement, LinkFinder):
|
||||
"""A super class for atom:feed and entry, contains shared attributes"""
|
||||
author = [Author]
|
||||
category = [Category]
|
||||
contributor = [Contributor]
|
||||
id = Id
|
||||
link = [Link]
|
||||
rights = Rights
|
||||
title = Title
|
||||
updated = Updated
|
||||
|
||||
def __init__(self, atom_id=None, text=None, *args, **kwargs):
|
||||
if atom_id is not None:
|
||||
self.id = atom_id
|
||||
atom.core.XmlElement.__init__(self, text=text, *args, **kwargs)
|
||||
|
||||
|
||||
class Source(FeedEntryParent):
|
||||
"""The atom:source element."""
|
||||
_qname = ATOM_TEMPLATE % 'source'
|
||||
generator = Generator
|
||||
icon = Icon
|
||||
logo = Logo
|
||||
subtitle = Subtitle
|
||||
|
||||
|
||||
class Entry(FeedEntryParent):
|
||||
"""The atom:entry element."""
|
||||
_qname = ATOM_TEMPLATE % 'entry'
|
||||
content = Content
|
||||
published = Published
|
||||
source = Source
|
||||
summary = Summary
|
||||
control = Control
|
||||
|
||||
|
||||
class Feed(Source):
|
||||
"""The atom:feed element which contains entries."""
|
||||
_qname = ATOM_TEMPLATE % 'feed'
|
||||
entry = [Entry]
|
||||
|
||||
|
||||
class ExtensionElement(atom.core.XmlElement):
|
||||
"""Provided for backwards compatibility to the v1 atom.ExtensionElement."""
|
||||
|
||||
def __init__(self, tag=None, namespace=None, attributes=None,
|
||||
children=None, text=None, *args, **kwargs):
|
||||
if namespace:
|
||||
self._qname = '{%s}%s' % (namespace, tag)
|
||||
else:
|
||||
self._qname = tag
|
||||
self.children = children or []
|
||||
self.attributes = attributes or {}
|
||||
self.text = text
|
||||
|
||||
_BecomeChildElement = atom.core.XmlElement._become_child
|
||||
@@ -1,354 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2008 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License 2.0;
|
||||
|
||||
|
||||
|
||||
"""HttpClients in this module use httplib to make HTTP requests.
|
||||
|
||||
This module make HTTP requests based on httplib, but there are environments
|
||||
in which an httplib based approach will not work (if running in Google App
|
||||
Engine for example). In those cases, higher level classes (like AtomService
|
||||
and GDataService) can swap out the HttpClient to transparently use a
|
||||
different mechanism for making HTTP requests.
|
||||
|
||||
HttpClient: Contains a request method which performs an HTTP call to the
|
||||
server.
|
||||
|
||||
ProxiedHttpClient: Contains a request method which connects to a proxy using
|
||||
settings stored in operating system environment variables then
|
||||
performs an HTTP call to the endpoint server.
|
||||
"""
|
||||
|
||||
# __author__ = 'api.jscudder (Jeff Scudder)'
|
||||
|
||||
import base64
|
||||
import http.client
|
||||
import os
|
||||
import socket
|
||||
|
||||
import atom.http_core
|
||||
import atom.http_interface
|
||||
import atom.url
|
||||
|
||||
ssl_imported = False
|
||||
ssl = None
|
||||
try:
|
||||
import ssl
|
||||
|
||||
ssl_imported = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class ProxyError(atom.http_interface.Error):
|
||||
pass
|
||||
|
||||
|
||||
class TestConfigurationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
DEFAULT_CONTENT_TYPE = 'application/atom+xml'
|
||||
|
||||
|
||||
class HttpClient(atom.http_interface.GenericHttpClient):
|
||||
# Added to allow old v1 HttpClient objects to use the new
|
||||
# http_code.HttpClient. Used in unit tests to inject a mock client.
|
||||
v2_http_client = None
|
||||
|
||||
def __init__(self, headers=None):
|
||||
self.debug = False
|
||||
self.headers = headers or {}
|
||||
|
||||
def request(self, operation, url, data=None, headers=None):
|
||||
"""Performs an HTTP call to the server, supports GET, POST, PUT, and
|
||||
DELETE.
|
||||
|
||||
Usage example, perform and HTTP GET on http://www.google.com/:
|
||||
import atom.http
|
||||
client = atom.http.HttpClient()
|
||||
http_response = client.request('GET', 'http://www.google.com/')
|
||||
|
||||
Args:
|
||||
operation: str The HTTP operation to be performed. This is usually one
|
||||
of 'GET', 'POST', 'PUT', or 'DELETE'
|
||||
data: filestream, list of parts, or other object which can be converted
|
||||
to a string. Should be set to None when performing a GET or DELETE.
|
||||
If data is a file-like object which can be read, this method will
|
||||
read a chunk of 100K bytes at a time and send them.
|
||||
If the data is a list of parts to be sent, each part will be
|
||||
evaluated and sent.
|
||||
url: The full URL to which the request should be sent. Can be a string
|
||||
or atom.url.Url.
|
||||
headers: dict of strings. HTTP headers which should be sent
|
||||
in the request.
|
||||
"""
|
||||
all_headers = self.headers.copy()
|
||||
if headers:
|
||||
all_headers.update(headers)
|
||||
|
||||
# If the list of headers does not include a Content-Length, attempt to
|
||||
# calculate it based on the data object.
|
||||
if data and 'Content-Length' not in all_headers:
|
||||
if isinstance(data, (str,)):
|
||||
all_headers['Content-Length'] = str(len(data))
|
||||
else:
|
||||
raise atom.http_interface.ContentLengthRequired('Unable to calculate '
|
||||
'the length of the data parameter. Specify a value for '
|
||||
'Content-Length')
|
||||
|
||||
# Set the content type to the default value if none was set.
|
||||
if 'Content-Type' not in all_headers:
|
||||
all_headers['Content-Type'] = DEFAULT_CONTENT_TYPE
|
||||
|
||||
if self.v2_http_client is not None:
|
||||
http_request = atom.http_core.HttpRequest(method=operation)
|
||||
atom.http_core.Uri.parse_uri(str(url)).modify_request(http_request)
|
||||
http_request.headers = all_headers
|
||||
if data:
|
||||
http_request._body_parts.append(data)
|
||||
return self.v2_http_client.request(http_request=http_request)
|
||||
|
||||
if not isinstance(url, atom.url.Url):
|
||||
if isinstance(url, str):
|
||||
url = atom.url.parse_url(url)
|
||||
else:
|
||||
raise atom.http_interface.UnparsableUrlObject('Unable to parse url parameter because it was not a string or atom.url.Url')
|
||||
|
||||
connection = self._prepare_connection(url, all_headers)
|
||||
|
||||
if self.debug:
|
||||
connection.debuglevel = 1
|
||||
|
||||
connection.putrequest(operation, self._get_access_url(url), skip_host=True)
|
||||
|
||||
if url.port is not None:
|
||||
connection.putheader('Host', '%s:%s' % (url.host, url.port))
|
||||
else:
|
||||
connection.putheader('Host', url.host)
|
||||
|
||||
# Overcome a bug in Python 2.4 and 2.5
|
||||
# httplib.HTTPConnection.putrequest adding
|
||||
# HTTP request header 'Host: www.google.com:443' instead of
|
||||
# 'Host: www.google.com', and thus resulting the error message
|
||||
# 'Token invalid - AuthSub token has wrong scope' in the HTTP response.
|
||||
if (url.protocol == 'https' and int(url.port or 443) == 443 and
|
||||
hasattr(connection, '_buffer') and
|
||||
isinstance(connection._buffer, list)):
|
||||
header_line = 'Host: %s:443' % url.host
|
||||
replacement_header_line = 'Host: %s' % url.host
|
||||
try:
|
||||
connection._buffer[connection._buffer.index(header_line)] = (
|
||||
replacement_header_line)
|
||||
except ValueError: # header_line missing from connection._buffer
|
||||
pass
|
||||
|
||||
# Send the HTTP headers.
|
||||
for header_name in all_headers:
|
||||
connection.putheader(header_name, all_headers[header_name])
|
||||
connection.endheaders()
|
||||
|
||||
# If there is data, send it in the request.
|
||||
if data:
|
||||
if isinstance(data, list):
|
||||
for data_part in data:
|
||||
_send_data_part(data_part, connection)
|
||||
else:
|
||||
_send_data_part(data, connection)
|
||||
|
||||
# Return the HTTP Response from the server.
|
||||
return connection.getresponse()
|
||||
|
||||
def _prepare_connection(self, url, headers):
|
||||
if not isinstance(url, atom.url.Url):
|
||||
if isinstance(url, (str,)):
|
||||
url = atom.url.parse_url(url)
|
||||
else:
|
||||
raise atom.http_interface.UnparsableUrlObject('Unable to parse url '
|
||||
'parameter because it was not a string or atom.url.Url')
|
||||
if url.protocol == 'https':
|
||||
if not url.port:
|
||||
return http.client.HTTPSConnection(url.host)
|
||||
return http.client.HTTPSConnection(url.host, int(url.port))
|
||||
else:
|
||||
if not url.port:
|
||||
return http.client.HTTPConnection(url.host)
|
||||
return http.client.HTTPConnection(url.host, int(url.port))
|
||||
|
||||
def _get_access_url(self, url):
|
||||
return url.to_string()
|
||||
|
||||
|
||||
class ProxiedHttpClient(HttpClient):
|
||||
"""Performs an HTTP request through a proxy.
|
||||
|
||||
The proxy settings are obtained from enviroment variables. The URL of the
|
||||
proxy server is assumed to be stored in the environment variables
|
||||
'https_proxy' and 'http_proxy' respectively. If the proxy server requires
|
||||
a Basic Auth authorization header, the username and password are expected to
|
||||
be in the 'proxy-username' or 'proxy_username' variable and the
|
||||
'proxy-password' or 'proxy_password' variable, or in 'http_proxy' or
|
||||
'https_proxy' as "protocol://[username:password@]host:port".
|
||||
|
||||
After connecting to the proxy server, the request is completed as in
|
||||
HttpClient.request.
|
||||
"""
|
||||
|
||||
def _prepare_connection(self, url, headers):
|
||||
proxy_settings = os.environ.get('%s_proxy' % url.protocol)
|
||||
if not proxy_settings:
|
||||
# The request was HTTP or HTTPS, but there was no appropriate proxy set.
|
||||
return HttpClient._prepare_connection(self, url, headers)
|
||||
else:
|
||||
proxy_auth = _get_proxy_auth(proxy_settings)
|
||||
proxy_netloc = _get_proxy_net_location(proxy_settings)
|
||||
if url.protocol == 'https':
|
||||
# Set any proxy auth headers
|
||||
if proxy_auth:
|
||||
proxy_auth = 'Proxy-Authorization: %s' % proxy_auth
|
||||
|
||||
# Construct the proxy connect command.
|
||||
port = url.port
|
||||
if not port:
|
||||
port = '443'
|
||||
proxy_connect = 'CONNECT %s:%s HTTP/1.0\r\n' % (url.host, port)
|
||||
|
||||
# Set the user agent to send to the proxy
|
||||
if headers and 'User-Agent' in headers:
|
||||
user_agent = 'User-Agent: %s\r\n' % (headers['User-Agent'])
|
||||
else:
|
||||
user_agent = 'User-Agent: python\r\n'
|
||||
|
||||
proxy_pieces = '%s%s%s\r\n' % (proxy_connect, proxy_auth, user_agent)
|
||||
|
||||
# Find the proxy host and port.
|
||||
proxy_url = atom.url.parse_url(proxy_netloc)
|
||||
if not proxy_url.port:
|
||||
proxy_url.port = '80'
|
||||
|
||||
# Connect to the proxy server, very simple recv and error checking
|
||||
p_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
p_sock.connect((proxy_url.host, int(proxy_url.port)))
|
||||
#p_sock.sendall(proxy_pieces)
|
||||
p_sock.sendall(proxy_pieces.encode('utf-8'))
|
||||
response = ''
|
||||
|
||||
# Wait for the full response.
|
||||
while response.find("\r\n\r\n") == -1:
|
||||
#response += p_sock.recv(8192)
|
||||
response += p_sock.recv(8192).decode('utf-8')
|
||||
|
||||
p_status = response.split()[1]
|
||||
if p_status != str(200):
|
||||
raise ProxyError('Error status=%s' % str(p_status))
|
||||
|
||||
# Trivial setup for ssl socket.
|
||||
sslobj = None
|
||||
if ssl_imported:
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
|
||||
context.minimum_version = ssl.TLSVersion.TLSv1_2
|
||||
sslobj = context.wrap_socket(p_sock, server_hostname=url.host)
|
||||
else:
|
||||
sock_ssl = socket.ssl(p_sock, None, None)
|
||||
sslobj = http.client.FakeSocket(p_sock, sock_ssl)
|
||||
|
||||
# Initalize httplib and replace with the proxy socket.
|
||||
connection = http.client.HTTPConnection(proxy_url.host)
|
||||
connection.sock = sslobj
|
||||
return connection
|
||||
else:
|
||||
# If protocol was not https.
|
||||
# Find the proxy host and port.
|
||||
proxy_url = atom.url.parse_url(proxy_netloc)
|
||||
if not proxy_url.port:
|
||||
proxy_url.port = '80'
|
||||
|
||||
if proxy_auth:
|
||||
headers['Proxy-Authorization'] = proxy_auth.strip()
|
||||
|
||||
return http.client.HTTPConnection(proxy_url.host, int(proxy_url.port))
|
||||
|
||||
def _get_access_url(self, url):
|
||||
return url.to_string()
|
||||
|
||||
|
||||
def _get_proxy_auth(proxy_settings):
|
||||
"""Returns proxy authentication string for header.
|
||||
|
||||
Will check environment variables for proxy authentication info, starting with
|
||||
proxy(_/-)username and proxy(_/-)password before checking the given
|
||||
proxy_settings for a [protocol://]username:password@host[:port] string.
|
||||
|
||||
Args:
|
||||
proxy_settings: String from http_proxy or https_proxy environment variable.
|
||||
|
||||
Returns:
|
||||
Authentication string for proxy, or empty string if no proxy username was
|
||||
found.
|
||||
"""
|
||||
proxy_username = None
|
||||
proxy_password = None
|
||||
|
||||
proxy_username = os.environ.get('proxy-username')
|
||||
if not proxy_username:
|
||||
proxy_username = os.environ.get('proxy_username')
|
||||
proxy_password = os.environ.get('proxy-password')
|
||||
if not proxy_password:
|
||||
proxy_password = os.environ.get('proxy_password')
|
||||
|
||||
if not proxy_username:
|
||||
if '@' in proxy_settings:
|
||||
protocol_and_proxy_auth = proxy_settings.split('@')[0].split(':')
|
||||
if len(protocol_and_proxy_auth) == 3:
|
||||
# 3 elements means we have [<protocol>, //<user>, <password>]
|
||||
proxy_username = protocol_and_proxy_auth[1].lstrip('/')
|
||||
proxy_password = protocol_and_proxy_auth[2]
|
||||
elif len(protocol_and_proxy_auth) == 2:
|
||||
# 2 elements means we have [<user>, <password>]
|
||||
proxy_username = protocol_and_proxy_auth[0]
|
||||
proxy_password = protocol_and_proxy_auth[1]
|
||||
if proxy_username:
|
||||
user_auth = base64.b64encode(('%s:%s' % (proxy_username, proxy_password)).encode('utf-8'))
|
||||
return 'Basic %s\r\n' % (user_auth.strip().decode('utf-8'))
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
def _get_proxy_net_location(proxy_settings):
|
||||
"""Returns proxy host and port.
|
||||
|
||||
Args:
|
||||
proxy_settings: String from http_proxy or https_proxy environment variable.
|
||||
Must be in the form of protocol://[username:password@]host:port
|
||||
|
||||
Returns:
|
||||
String in the form of protocol://host:port
|
||||
"""
|
||||
if '@' in proxy_settings:
|
||||
protocol = proxy_settings.split(':')[0]
|
||||
netloc = proxy_settings.split('@')[1]
|
||||
return '%s://%s' % (protocol, netloc)
|
||||
else:
|
||||
return proxy_settings
|
||||
|
||||
|
||||
def _send_data_part(data, connection):
|
||||
if isinstance(data, (str,)):
|
||||
connection.send(data)
|
||||
return
|
||||
# Check to see if data is a file-like object that has a read method.
|
||||
elif hasattr(data, 'read'):
|
||||
# Read the file and send it a chunk at a time.
|
||||
while 1:
|
||||
binarydata = data.read(100000)
|
||||
if binarydata == b'': break
|
||||
connection.send(binarydata)
|
||||
return
|
||||
else:
|
||||
# The data object was not a file.
|
||||
# Try to convert to a string and send the data.
|
||||
#connection.send(str(data))
|
||||
connection.send(str(data).encode('utf-8'))
|
||||
return
|
||||
@@ -1,599 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2009 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License 2.0;
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
# This module is used for version 2 of the Google Data APIs.
|
||||
# TODO: add proxy handling.
|
||||
|
||||
|
||||
# __author__ = 'j.s@google.com (Jeff Scudder)'
|
||||
|
||||
import http.client
|
||||
import io
|
||||
import os
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
ssl = None
|
||||
try:
|
||||
import ssl
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownSize(Error):
|
||||
pass
|
||||
|
||||
|
||||
class ProxyError(Error):
|
||||
pass
|
||||
|
||||
|
||||
MIME_BOUNDARY = 'END_OF_PART'
|
||||
|
||||
|
||||
def get_headers(http_response):
|
||||
"""Retrieves all HTTP headers from an HTTP response from the server.
|
||||
|
||||
This method is provided for backwards compatibility for Python2.2 and 2.3.
|
||||
The httplib.HTTPResponse object in 2.2 and 2.3 does not have a getheaders
|
||||
method so this function will use getheaders if available, but if not it
|
||||
will retrieve a few using getheader.
|
||||
"""
|
||||
if hasattr(http_response, 'getheaders'):
|
||||
return http_response.getheaders()
|
||||
else:
|
||||
headers = []
|
||||
for header in (
|
||||
'location', 'content-type', 'content-length', 'age', 'allow',
|
||||
'cache-control', 'content-location', 'content-encoding', 'date',
|
||||
'etag', 'expires', 'last-modified', 'pragma', 'server',
|
||||
'set-cookie', 'transfer-encoding', 'vary', 'via', 'warning',
|
||||
'www-authenticate', 'gdata-version'):
|
||||
value = http_response.getheader(header, None)
|
||||
if value is not None:
|
||||
headers.append((header, value))
|
||||
return headers
|
||||
|
||||
|
||||
class HttpRequest(object):
|
||||
"""Contains all of the parameters for an HTTP 1.1 request.
|
||||
|
||||
The HTTP headers are represented by a dictionary, and it is the
|
||||
responsibility of the user to ensure that duplicate field names are combined
|
||||
into one header value according to the rules in section 4.2 of RFC 2616.
|
||||
"""
|
||||
method = None
|
||||
uri = None
|
||||
|
||||
def __init__(self, uri=None, method=None, headers=None):
|
||||
"""Construct an HTTP request.
|
||||
|
||||
Args:
|
||||
uri: The full path or partial path as a Uri object or a string.
|
||||
method: The HTTP method for the request, examples include 'GET', 'POST',
|
||||
etc.
|
||||
headers: dict of strings The HTTP headers to include in the request.
|
||||
"""
|
||||
self.headers = headers or {}
|
||||
self._body_parts = []
|
||||
if method is not None:
|
||||
self.method = method
|
||||
if isinstance(uri, str):
|
||||
uri = Uri.parse_uri(uri)
|
||||
self.uri = uri or Uri()
|
||||
|
||||
def add_body_part(self, data, mime_type, size=None):
|
||||
"""Adds data to the HTTP request body.
|
||||
|
||||
If more than one part is added, this is assumed to be a mime-multipart
|
||||
request. This method is designed to create MIME 1.0 requests as specified
|
||||
in RFC 1341.
|
||||
|
||||
Args:
|
||||
data: str or a file-like object containing a part of the request body.
|
||||
mime_type: str The MIME type describing the data
|
||||
size: int Required if the data is a file like object. If the data is a
|
||||
string, the size is calculated so this parameter is ignored.
|
||||
"""
|
||||
if hasattr(data, '__len__'):
|
||||
size = len(data)
|
||||
if size is None:
|
||||
# TODO: support chunked transfer if some of the body is of unknown size.
|
||||
raise UnknownSize('Each part of the body must have a known size.')
|
||||
if 'Content-Length' in self.headers:
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
else:
|
||||
content_length = 0
|
||||
# If this is the first part added to the body, then this is not a multipart
|
||||
# request.
|
||||
if len(self._body_parts) == 0:
|
||||
self.headers['Content-Type'] = mime_type
|
||||
content_length = size
|
||||
self._body_parts.append(data)
|
||||
elif len(self._body_parts) == 1:
|
||||
# This is the first member in a mime-multipart request, so change the
|
||||
# _body_parts list to indicate a multipart payload.
|
||||
self._body_parts.insert(0, 'Media multipart posting')
|
||||
boundary_string = '\r\n--%s\r\n' % (MIME_BOUNDARY,)
|
||||
content_length += len(boundary_string) + size
|
||||
self._body_parts.insert(1, boundary_string)
|
||||
content_length += len('Media multipart posting')
|
||||
# Put the content type of the first part of the body into the multipart
|
||||
# payload.
|
||||
original_type_string = 'Content-Type: %s\r\n\r\n' % (
|
||||
self.headers['Content-Type'],)
|
||||
self._body_parts.insert(2, original_type_string)
|
||||
content_length += len(original_type_string)
|
||||
boundary_string = '\r\n--%s\r\n' % (MIME_BOUNDARY,)
|
||||
self._body_parts.append(boundary_string)
|
||||
content_length += len(boundary_string)
|
||||
# Change the headers to indicate this is now a mime multipart request.
|
||||
self.headers['Content-Type'] = 'multipart/related; boundary="%s"' % (
|
||||
MIME_BOUNDARY,)
|
||||
self.headers['MIME-version'] = '1.0'
|
||||
# Include the mime type of this part.
|
||||
type_string = 'Content-Type: %s\r\n\r\n' % (mime_type)
|
||||
self._body_parts.append(type_string)
|
||||
content_length += len(type_string)
|
||||
self._body_parts.append(data)
|
||||
ending_boundary_string = '\r\n--%s--' % (MIME_BOUNDARY,)
|
||||
self._body_parts.append(ending_boundary_string)
|
||||
content_length += len(ending_boundary_string)
|
||||
else:
|
||||
# This is a mime multipart request.
|
||||
boundary_string = '\r\n--%s\r\n' % (MIME_BOUNDARY,)
|
||||
self._body_parts.insert(-1, boundary_string)
|
||||
content_length += len(boundary_string) + size
|
||||
# Include the mime type of this part.
|
||||
type_string = 'Content-Type: %s\r\n\r\n' % (mime_type)
|
||||
self._body_parts.insert(-1, type_string)
|
||||
content_length += len(type_string)
|
||||
self._body_parts.insert(-1, data)
|
||||
self.headers['Content-Length'] = str(content_length)
|
||||
|
||||
# I could add an "append_to_body_part" method as well.
|
||||
|
||||
AddBodyPart = add_body_part
|
||||
|
||||
def add_form_inputs(self, form_data,
|
||||
mime_type='application/x-www-form-urlencoded'):
|
||||
"""Form-encodes and adds data to the request body.
|
||||
|
||||
Args:
|
||||
form_data: dict or sequnce or two member tuples which contains the
|
||||
form keys and values.
|
||||
mime_type: str The MIME type of the form data being sent. Defaults
|
||||
to 'application/x-www-form-urlencoded'.
|
||||
"""
|
||||
body = urllib.parse.urlencode(form_data)
|
||||
self.add_body_part(body, bytes(mime_type, 'ascii'))
|
||||
|
||||
AddFormInputs = add_form_inputs
|
||||
|
||||
def _copy(self):
|
||||
"""Creates a deep copy of this request."""
|
||||
copied_uri = Uri(self.uri.scheme, self.uri.host, self.uri.port,
|
||||
self.uri.path, self.uri.query.copy())
|
||||
new_request = HttpRequest(uri=copied_uri, method=self.method,
|
||||
headers=self.headers.copy())
|
||||
new_request._body_parts = self._body_parts[:]
|
||||
return new_request
|
||||
|
||||
def _dump(self):
|
||||
"""Converts to a printable string for debugging purposes.
|
||||
|
||||
In order to preserve the request, it does not read from file-like objects
|
||||
in the body.
|
||||
"""
|
||||
output = 'HTTP Request\n method: %s\n url: %s\n headers:\n' % (
|
||||
self.method, str(self.uri))
|
||||
for header, value in self.headers.items():
|
||||
output += ' %s: %s\n' % (header, value)
|
||||
output += ' body sections:\n'
|
||||
i = 0
|
||||
for part in self._body_parts:
|
||||
if isinstance(part, str):
|
||||
output += ' %s: %s\n' % (i, part)
|
||||
else:
|
||||
output += ' %s: <file like object>\n' % i
|
||||
i += 1
|
||||
return output
|
||||
|
||||
|
||||
def _apply_defaults(http_request):
|
||||
if http_request.uri.scheme is None:
|
||||
if http_request.uri.port == 443:
|
||||
http_request.uri.scheme = 'https'
|
||||
else:
|
||||
http_request.uri.scheme = 'http'
|
||||
|
||||
|
||||
class Uri(object):
|
||||
"""A URI as used in HTTP 1.1"""
|
||||
scheme = None
|
||||
host = None
|
||||
port = None
|
||||
path = None
|
||||
|
||||
def __init__(self, scheme=None, host=None, port=None, path=None, query=None):
|
||||
"""Constructor for a URI.
|
||||
|
||||
Args:
|
||||
scheme: str This is usually 'http' or 'https'.
|
||||
host: str The host name or IP address of the desired server.
|
||||
post: int The server's port number.
|
||||
path: str The path of the resource following the host. This begins with
|
||||
a /, example: '/calendar/feeds/default/allcalendars/full'
|
||||
query: dict of strings The URL query parameters. The keys and values are
|
||||
both escaped so this dict should contain the unescaped values.
|
||||
For example {'my key': 'val', 'second': '!!!'} will become
|
||||
'?my+key=val&second=%21%21%21' which is appended to the path.
|
||||
"""
|
||||
self.query = query or {}
|
||||
if scheme is not None:
|
||||
self.scheme = scheme
|
||||
if host is not None:
|
||||
self.host = host
|
||||
if port is not None:
|
||||
self.port = port
|
||||
if path:
|
||||
self.path = path
|
||||
|
||||
def _get_query_string(self):
|
||||
param_pairs = []
|
||||
for key, value in self.query.items():
|
||||
quoted_key = urllib.parse.quote_plus(str(key))
|
||||
if value is None:
|
||||
param_pairs.append(quoted_key)
|
||||
else:
|
||||
quoted_value = urllib.parse.quote_plus(str(value))
|
||||
param_pairs.append('%s=%s' % (quoted_key, quoted_value))
|
||||
return '&'.join(param_pairs)
|
||||
|
||||
def _get_relative_path(self):
|
||||
"""Returns the path with the query parameters escaped and appended."""
|
||||
param_string = self._get_query_string()
|
||||
if self.path is None:
|
||||
path = '/'
|
||||
else:
|
||||
path = self.path
|
||||
if param_string:
|
||||
return '?'.join([path, param_string])
|
||||
else:
|
||||
return path
|
||||
|
||||
def _to_string(self):
|
||||
if self.scheme is None and self.port == 443:
|
||||
scheme = 'https'
|
||||
elif self.scheme is None:
|
||||
scheme = 'http'
|
||||
else:
|
||||
scheme = self.scheme
|
||||
if self.path is None:
|
||||
path = '/'
|
||||
else:
|
||||
path = self.path
|
||||
if self.port is None:
|
||||
return '%s://%s%s' % (scheme, self.host, self._get_relative_path())
|
||||
else:
|
||||
return '%s://%s:%s%s' % (scheme, self.host, str(self.port),
|
||||
self._get_relative_path())
|
||||
|
||||
def __str__(self):
|
||||
return self._to_string()
|
||||
|
||||
def modify_request(self, http_request=None):
|
||||
"""Sets HTTP request components based on the URI."""
|
||||
if http_request is None:
|
||||
http_request = HttpRequest()
|
||||
if http_request.uri is None:
|
||||
http_request.uri = Uri()
|
||||
# Determine the correct scheme.
|
||||
if self.scheme:
|
||||
http_request.uri.scheme = self.scheme
|
||||
if self.port:
|
||||
http_request.uri.port = self.port
|
||||
if self.host:
|
||||
http_request.uri.host = self.host
|
||||
# Set the relative uri path
|
||||
if self.path:
|
||||
http_request.uri.path = self.path
|
||||
if self.query:
|
||||
http_request.uri.query = self.query.copy()
|
||||
return http_request
|
||||
|
||||
ModifyRequest = modify_request
|
||||
|
||||
def parse_uri(uri_string):
|
||||
"""Creates a Uri object which corresponds to the URI string.
|
||||
|
||||
This method can accept partial URIs, but it will leave missing
|
||||
members of the Uri unset.
|
||||
"""
|
||||
parts = urllib.parse.urlparse(uri_string)
|
||||
uri = Uri()
|
||||
if parts[0]:
|
||||
uri.scheme = parts[0]
|
||||
if parts[1]:
|
||||
host_parts = parts[1].split(':')
|
||||
if host_parts[0]:
|
||||
uri.host = host_parts[0]
|
||||
if len(host_parts) > 1:
|
||||
uri.port = int(host_parts[1])
|
||||
if parts[2]:
|
||||
uri.path = parts[2]
|
||||
if parts[4]:
|
||||
param_pairs = parts[4].split('&')
|
||||
for pair in param_pairs:
|
||||
pair_parts = pair.split('=')
|
||||
if len(pair_parts) > 1:
|
||||
uri.query[urllib.parse.unquote_plus(pair_parts[0])] = (
|
||||
urllib.parse.unquote_plus(pair_parts[1]))
|
||||
elif len(pair_parts) == 1:
|
||||
uri.query[urllib.parse.unquote_plus(pair_parts[0])] = None
|
||||
return uri
|
||||
|
||||
parse_uri = staticmethod(parse_uri)
|
||||
|
||||
ParseUri = parse_uri
|
||||
|
||||
|
||||
parse_uri = Uri.parse_uri
|
||||
|
||||
ParseUri = Uri.parse_uri
|
||||
|
||||
|
||||
class HttpResponse(object):
|
||||
status = None
|
||||
reason = None
|
||||
_body = None
|
||||
|
||||
def __init__(self, status=None, reason=None, headers=None, body=None):
|
||||
self._headers = headers or {}
|
||||
if status is not None:
|
||||
self.status = status
|
||||
if reason is not None:
|
||||
self.reason = reason
|
||||
if body is not None:
|
||||
if hasattr(body, 'read'):
|
||||
self._body = body
|
||||
else:
|
||||
self._body = io.StringIO(body)
|
||||
|
||||
def getheader(self, name, default=None):
|
||||
if name in self._headers:
|
||||
return self._headers[name]
|
||||
else:
|
||||
return default
|
||||
|
||||
def getheaders(self):
|
||||
return self._headers
|
||||
|
||||
def read(self, amt=None):
|
||||
if self._body is None:
|
||||
return None
|
||||
if not amt:
|
||||
return self._body.read()
|
||||
else:
|
||||
return self._body.read(amt)
|
||||
|
||||
|
||||
def _dump_response(http_response):
|
||||
"""Converts to a string for printing debug messages.
|
||||
|
||||
Does not read the body since that may consume the content.
|
||||
"""
|
||||
output = 'HttpResponse\n status: %s\n reason: %s\n headers:' % (
|
||||
http_response.status, http_response.reason)
|
||||
headers = get_headers(http_response)
|
||||
if isinstance(headers, dict):
|
||||
for header, value in headers.items():
|
||||
output += ' %s: %s\n' % (header, value)
|
||||
else:
|
||||
for pair in headers:
|
||||
output += ' %s: %s\n' % (pair[0], pair[1])
|
||||
return output
|
||||
|
||||
|
||||
class HttpClient(object):
|
||||
"""Performs HTTP requests using httplib."""
|
||||
debug = None
|
||||
|
||||
def request(self, http_request):
|
||||
return self._http_request(http_request.method, http_request.uri,
|
||||
http_request.headers, http_request._body_parts)
|
||||
|
||||
Request = request
|
||||
|
||||
def _get_connection(self, uri, headers=None):
|
||||
"""Opens a socket connection to the server to set up an HTTP request.
|
||||
|
||||
Args:
|
||||
uri: The full URL for the request as a Uri object.
|
||||
headers: A dict of string pairs containing the HTTP headers for the
|
||||
request.
|
||||
"""
|
||||
connection = None
|
||||
if uri.scheme == 'https':
|
||||
if not uri.port:
|
||||
connection = http.client.HTTPSConnection(uri.host)
|
||||
else:
|
||||
connection = http.client.HTTPSConnection(uri.host, int(uri.port))
|
||||
else:
|
||||
if not uri.port:
|
||||
connection = http.client.HTTPConnection(uri.host)
|
||||
else:
|
||||
connection = http.client.HTTPConnection(uri.host, int(uri.port))
|
||||
return connection
|
||||
|
||||
def _http_request(self, method, uri, headers=None, body_parts=None):
|
||||
"""Makes an HTTP request using httplib.
|
||||
|
||||
Args:
|
||||
method: str example: 'GET', 'POST', 'PUT', 'DELETE', etc.
|
||||
uri: str or atom.http_core.Uri
|
||||
headers: dict of strings mapping to strings which will be sent as HTTP
|
||||
headers in the request.
|
||||
body_parts: list of strings, objects with a read method, or objects
|
||||
which can be converted to strings using str. Each of these
|
||||
will be sent in order as the body of the HTTP request.
|
||||
"""
|
||||
if isinstance(uri, str):
|
||||
uri = Uri.parse_uri(uri)
|
||||
|
||||
connection = self._get_connection(uri, headers=headers)
|
||||
|
||||
if self.debug:
|
||||
connection.debuglevel = 1
|
||||
|
||||
if connection.host != uri.host:
|
||||
connection.putrequest(method, str(uri))
|
||||
else:
|
||||
connection.putrequest(method, uri._get_relative_path())
|
||||
|
||||
# Overcome a bug in Python 2.4 and 2.5
|
||||
# httplib.HTTPConnection.putrequest adding
|
||||
# HTTP request header 'Host: www.google.com:443' instead of
|
||||
# 'Host: www.google.com', and thus resulting the error message
|
||||
# 'Token invalid - AuthSub token has wrong scope' in the HTTP response.
|
||||
if (uri.scheme == 'https' and int(uri.port or 443) == 443 and
|
||||
hasattr(connection, '_buffer') and
|
||||
isinstance(connection._buffer, list)):
|
||||
header_line = 'Host: %s:443' % uri.host
|
||||
replacement_header_line = 'Host: %s' % uri.host
|
||||
try:
|
||||
connection._buffer[connection._buffer.index(header_line)] = (
|
||||
replacement_header_line)
|
||||
except ValueError: # header_line missing from connection._buffer
|
||||
pass
|
||||
|
||||
# Send the HTTP headers.
|
||||
for header_name, value in headers.items():
|
||||
connection.putheader(header_name, value)
|
||||
connection.endheaders()
|
||||
|
||||
# If there is data, send it in the request.
|
||||
if body_parts and [x for x in body_parts if x != '']:
|
||||
for part in body_parts:
|
||||
_send_data_part(part, connection)
|
||||
|
||||
# Return the HTTP Response from the server.
|
||||
return connection.getresponse()
|
||||
|
||||
|
||||
def _send_data_part(data, connection):
|
||||
if isinstance(data, str):
|
||||
# I might want to just allow str, not unicode.
|
||||
connection.send(data.encode())
|
||||
# Check to see if data is a file-like object that has a read method.
|
||||
elif hasattr(data, 'read'):
|
||||
# Read the file and send it a chunk at a time.
|
||||
while 1:
|
||||
binarydata = data.read(100000)
|
||||
if binarydata == '': break
|
||||
connection.send(binarydata)
|
||||
else:
|
||||
# The data object was not a file.
|
||||
# Try to convert to a string and send the data.
|
||||
connection.send(data)
|
||||
|
||||
|
||||
class ProxiedHttpClient(HttpClient):
|
||||
def _get_connection(self, uri, headers=None):
|
||||
# Check to see if there are proxy settings required for this request.
|
||||
proxy = None
|
||||
if uri.scheme == 'https':
|
||||
proxy = os.environ.get('https_proxy')
|
||||
elif uri.scheme == 'http':
|
||||
proxy = os.environ.get('http_proxy')
|
||||
if not proxy:
|
||||
return HttpClient._get_connection(self, uri, headers=headers)
|
||||
# Now we have the URL of the appropriate proxy server.
|
||||
# Get a username and password for the proxy if required.
|
||||
proxy_auth = _get_proxy_auth()
|
||||
if uri.scheme == 'https':
|
||||
import socket
|
||||
if proxy_auth:
|
||||
proxy_auth = 'Proxy-Authorization: %s' % proxy_auth
|
||||
# Construct the proxy connect command.
|
||||
port = uri.port
|
||||
if not port:
|
||||
port = 443
|
||||
proxy_connect = 'CONNECT %s:%s HTTP/1.0\r\n' % (uri.host, port)
|
||||
# Set the user agent to send to the proxy
|
||||
user_agent = ''
|
||||
if headers and 'User-Agent' in headers:
|
||||
user_agent = 'User-Agent: %s\r\n' % (headers['User-Agent'])
|
||||
proxy_pieces = '%s%s%s\r\n' % (proxy_connect, proxy_auth, user_agent)
|
||||
# Find the proxy host and port.
|
||||
proxy_uri = Uri.parse_uri(proxy)
|
||||
if not proxy_uri.port:
|
||||
proxy_uri.port = '80'
|
||||
# Connect to the proxy server, very simple recv and error checking
|
||||
p_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
p_sock.connect((proxy_uri.host, int(proxy_uri.port)))
|
||||
#p_sock.sendall(proxy_pieces)
|
||||
p_sock.sendall(proxy_pieces.encode('utf-8'))
|
||||
response = ''
|
||||
# Wait for the full response.
|
||||
while response.find("\r\n\r\n") == -1:
|
||||
#response += p_sock.recv(8192)
|
||||
response += p_sock.recv(8192).decode('utf-8')
|
||||
p_status = response.split()[1]
|
||||
if p_status != str(200):
|
||||
raise ProxyError('Error status=%s' % str(p_status))
|
||||
# Trivial setup for ssl socket.
|
||||
sslobj = None
|
||||
if ssl is not None:
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
|
||||
context.minimum_version = ssl.TLSVersion.TLSv1_2
|
||||
sslobj = context.wrap_socket(p_sock, server_hostname=uri.host)
|
||||
else:
|
||||
sock_ssl = socket.ssl(p_sock, None, None)
|
||||
sslobj = http.client.FakeSocket(p_sock, sock_ssl)
|
||||
# Initalize httplib and replace with the proxy socket.
|
||||
connection = http.client.HTTPConnection(proxy_uri.host)
|
||||
connection.sock = sslobj
|
||||
return connection
|
||||
elif uri.scheme == 'http':
|
||||
proxy_uri = Uri.parse_uri(proxy)
|
||||
if not proxy_uri.port:
|
||||
proxy_uri.port = '80'
|
||||
if proxy_auth:
|
||||
headers['Proxy-Authorization'] = proxy_auth.strip()
|
||||
return http.client.HTTPConnection(proxy_uri.host, int(proxy_uri.port))
|
||||
return None
|
||||
|
||||
|
||||
def _get_proxy_auth():
|
||||
import base64
|
||||
proxy_username = os.environ.get('proxy-username')
|
||||
if not proxy_username:
|
||||
proxy_username = os.environ.get('proxy_username')
|
||||
proxy_password = os.environ.get('proxy-password')
|
||||
if not proxy_password:
|
||||
proxy_password = os.environ.get('proxy_password')
|
||||
if proxy_username:
|
||||
user_auth = base64.b64encode(('%s:%s' % (proxy_username, proxy_password)).encode('utf-8'))
|
||||
return 'Basic %s\r\n' % (user_auth.strip().decode('utf-8'))
|
||||
else:
|
||||
return ''
|
||||
@@ -1,144 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2008 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License 2.0;
|
||||
|
||||
|
||||
"""This module provides a common interface for all HTTP requests.
|
||||
|
||||
HttpResponse: Represents the server's response to an HTTP request. Provides
|
||||
an interface identical to httplib.HTTPResponse which is the response
|
||||
expected from higher level classes which use HttpClient.request.
|
||||
|
||||
GenericHttpClient: Provides an interface (superclass) for an object
|
||||
responsible for making HTTP requests. Subclasses of this object are
|
||||
used in AtomService and GDataService to make requests to the server. By
|
||||
changing the http_client member object, the AtomService is able to make
|
||||
HTTP requests using different logic (for example, when running on
|
||||
Google App Engine, the http_client makes requests using the App Engine
|
||||
urlfetch API).
|
||||
"""
|
||||
|
||||
# __author__ = 'api.jscudder (Jeff Scudder)'
|
||||
|
||||
import io
|
||||
|
||||
USER_AGENT = '%s GData-Python/2.0.18'
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnparsableUrlObject(Error):
|
||||
pass
|
||||
|
||||
|
||||
class ContentLengthRequired(Error):
|
||||
pass
|
||||
|
||||
|
||||
class HttpResponse(object):
|
||||
def __init__(self, body=None, status=None, reason=None, headers=None):
|
||||
"""Constructor for an HttpResponse object.
|
||||
|
||||
HttpResponse represents the server's response to an HTTP request from
|
||||
the client. The HttpClient.request method returns a httplib.HTTPResponse
|
||||
object and this HttpResponse class is designed to mirror the interface
|
||||
exposed by httplib.HTTPResponse.
|
||||
|
||||
Args:
|
||||
body: A file like object, with a read() method. The body could also
|
||||
be a string, and the constructor will wrap it so that
|
||||
HttpResponse.read(self) will return the full string.
|
||||
status: The HTTP status code as an int. Example: 200, 201, 404.
|
||||
reason: The HTTP status message which follows the code. Example:
|
||||
OK, Created, Not Found
|
||||
headers: A dictionary containing the HTTP headers in the server's
|
||||
response. A common header in the response is Content-Length.
|
||||
"""
|
||||
if body:
|
||||
if hasattr(body, 'read'):
|
||||
self._body = body
|
||||
else:
|
||||
self._body = io.StringIO(body)
|
||||
else:
|
||||
self._body = None
|
||||
if status is not None:
|
||||
self.status = int(status)
|
||||
else:
|
||||
self.status = None
|
||||
self.reason = reason
|
||||
self._headers = headers or {}
|
||||
|
||||
def getheader(self, name, default=None):
|
||||
if name in self._headers:
|
||||
return self._headers[name]
|
||||
else:
|
||||
return default
|
||||
|
||||
def read(self, amt=None):
|
||||
if not amt:
|
||||
return self._body.read()
|
||||
else:
|
||||
return self._body.read(amt)
|
||||
|
||||
|
||||
class GenericHttpClient(object):
|
||||
debug = False
|
||||
|
||||
def __init__(self, http_client, headers=None):
|
||||
"""
|
||||
|
||||
Args:
|
||||
http_client: An object which provides a request method to make an HTTP
|
||||
request. The request method in GenericHttpClient performs a
|
||||
call-through to the contained HTTP client object.
|
||||
headers: A dictionary containing HTTP headers which should be included
|
||||
in every HTTP request. Common persistent headers include
|
||||
'User-Agent'.
|
||||
"""
|
||||
self.http_client = http_client
|
||||
self.headers = headers or {}
|
||||
|
||||
def request(self, operation, url, data=None, headers=None):
|
||||
all_headers = self.headers.copy()
|
||||
if headers:
|
||||
all_headers.update(headers)
|
||||
return self.http_client.request(operation, url, data=data,
|
||||
headers=all_headers)
|
||||
|
||||
def get(self, url, headers=None):
|
||||
return self.request('GET', url, headers=headers)
|
||||
|
||||
def post(self, url, data, headers=None):
|
||||
return self.request('POST', url, data=data, headers=headers)
|
||||
|
||||
def put(self, url, data, headers=None):
|
||||
return self.request('PUT', url, data=data, headers=headers)
|
||||
|
||||
def delete(self, url, headers=None):
|
||||
return self.request('DELETE', url, headers=headers)
|
||||
|
||||
|
||||
class GenericToken(object):
|
||||
"""Represents an Authorization token to be added to HTTP requests.
|
||||
|
||||
Some Authorization headers included calculated fields (digital
|
||||
signatures for example) which are based on the parameters of the HTTP
|
||||
request. Therefore the token is responsible for signing the request
|
||||
and adding the Authorization header.
|
||||
"""
|
||||
|
||||
def perform_request(self, http_client, operation, url, data=None,
|
||||
headers=None):
|
||||
"""For the GenericToken, no Authorization token is set."""
|
||||
return http_client.request(operation, url, data=data, headers=headers)
|
||||
|
||||
def valid_for_scope(self, url):
|
||||
"""Tells the caller if the token authorizes access to the desired URL.
|
||||
|
||||
Since the generic token doesn't add an auth header, it is not valid for
|
||||
any scope.
|
||||
"""
|
||||
return False
|
||||
@@ -1,123 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2008 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License 2.0;
|
||||
|
||||
|
||||
|
||||
# __author__ = 'api.jscudder (Jeff Scudder)'
|
||||
|
||||
import atom.http_interface
|
||||
import atom.url
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NoRecordingFound(Error):
|
||||
pass
|
||||
|
||||
|
||||
class MockRequest(object):
|
||||
"""Holds parameters of an HTTP request for matching against future requests.
|
||||
"""
|
||||
|
||||
def __init__(self, operation, url, data=None, headers=None):
|
||||
self.operation = operation
|
||||
if isinstance(url, str):
|
||||
url = atom.url.parse_url(url)
|
||||
self.url = url
|
||||
self.data = data
|
||||
self.headers = headers
|
||||
|
||||
|
||||
class MockResponse(atom.http_interface.HttpResponse):
|
||||
"""Simulates an httplib.HTTPResponse object."""
|
||||
|
||||
def __init__(self, body=None, status=None, reason=None, headers=None):
|
||||
if body and hasattr(body, 'read'):
|
||||
self.body = body.read()
|
||||
else:
|
||||
self.body = body
|
||||
if status is not None:
|
||||
self.status = int(status)
|
||||
else:
|
||||
self.status = None
|
||||
self.reason = reason
|
||||
self._headers = headers or {}
|
||||
|
||||
def read(self):
|
||||
return self.body
|
||||
|
||||
|
||||
class MockHttpClient(atom.http_interface.GenericHttpClient):
|
||||
def __init__(self, headers=None, recordings=None, real_client=None):
|
||||
"""An HttpClient which responds to request with stored data.
|
||||
|
||||
The request-response pairs are stored as tuples in a member list named
|
||||
recordings.
|
||||
|
||||
The MockHttpClient can be switched from replay mode to record mode by
|
||||
setting the real_client member to an instance of an HttpClient which will
|
||||
make real HTTP requests and store the server's response in list of
|
||||
recordings.
|
||||
|
||||
Args:
|
||||
headers: dict containing HTTP headers which should be included in all
|
||||
HTTP requests.
|
||||
recordings: The initial recordings to be used for responses. This list
|
||||
contains tuples in the form: (MockRequest, MockResponse)
|
||||
real_client: An HttpClient which will make a real HTTP request. The
|
||||
response will be converted into a MockResponse and stored in
|
||||
recordings.
|
||||
"""
|
||||
self.recordings = recordings or []
|
||||
self.real_client = real_client
|
||||
self.headers = headers or {}
|
||||
|
||||
def add_response(self, response, operation, url, data=None, headers=None):
|
||||
"""Adds a request-response pair to the recordings list.
|
||||
|
||||
After the recording is added, future matching requests will receive the
|
||||
response.
|
||||
|
||||
Args:
|
||||
response: MockResponse
|
||||
operation: str
|
||||
url: str
|
||||
data: str, Currently the data is ignored when looking for matching
|
||||
requests.
|
||||
headers: dict of strings: Currently the headers are ignored when
|
||||
looking for matching requests.
|
||||
"""
|
||||
request = MockRequest(operation, url, data=data, headers=headers)
|
||||
self.recordings.append((request, response))
|
||||
|
||||
def request(self, operation, url, data=None, headers=None):
|
||||
"""Returns a matching MockResponse from the recordings.
|
||||
|
||||
If the real_client is set, the request will be passed along and the
|
||||
server's response will be added to the recordings and also returned.
|
||||
|
||||
If there is no match, a NoRecordingFound error will be raised.
|
||||
"""
|
||||
if self.real_client is None:
|
||||
if isinstance(url, str):
|
||||
url = atom.url.parse_url(url)
|
||||
for recording in self.recordings:
|
||||
if recording[0].operation == operation and recording[0].url == url:
|
||||
return recording[1]
|
||||
raise NoRecordingFound('No recordings found for %s %s' % (
|
||||
operation, url))
|
||||
else:
|
||||
# There is a real HTTP client, so make the request, and record the
|
||||
# response.
|
||||
response = self.real_client.request(operation, url, data=data,
|
||||
headers=headers)
|
||||
# TODO: copy the headers
|
||||
stored_response = MockResponse(body=response, status=response.status,
|
||||
reason=response.reason)
|
||||
self.add_response(stored_response, operation, url, data=data,
|
||||
headers=headers)
|
||||
return stored_response
|
||||
@@ -1,313 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2009 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License 2.0;
|
||||
|
||||
|
||||
|
||||
# This module is used for version 2 of the Google Data APIs.
|
||||
|
||||
|
||||
# __author__ = 'j.s@google.com (Jeff Scudder)'
|
||||
|
||||
import io
|
||||
import os.path
|
||||
import pickle
|
||||
import tempfile
|
||||
|
||||
import atom.http_core
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NoRecordingFound(Error):
|
||||
pass
|
||||
|
||||
|
||||
class MockHttpClient(object):
|
||||
debug = None
|
||||
real_client = None
|
||||
last_request_was_live = False
|
||||
|
||||
# The following members are used to construct the session cache temp file
|
||||
# name.
|
||||
# These are combined to form the file name
|
||||
# /tmp/cache_prefix.cache_case_name.cache_test_name
|
||||
cache_name_prefix = 'gdata_live_test'
|
||||
cache_case_name = ''
|
||||
cache_test_name = ''
|
||||
|
||||
def __init__(self, recordings=None, real_client=None):
|
||||
self._recordings = recordings or []
|
||||
if real_client is not None:
|
||||
self.real_client = real_client
|
||||
|
||||
def add_response(self, http_request, status, reason, headers=None,
|
||||
body=None):
|
||||
response = MockHttpResponse(status, reason, headers, body)
|
||||
# TODO Scrub the request and the response.
|
||||
self._recordings.append((http_request._copy(), response))
|
||||
|
||||
AddResponse = add_response
|
||||
|
||||
def request(self, http_request):
|
||||
"""Provide a recorded response, or record a response for replay.
|
||||
|
||||
If the real_client is set, the request will be made using the
|
||||
real_client, and the response from the server will be recorded.
|
||||
If the real_client is None (the default), this method will examine
|
||||
the recordings and find the first which matches.
|
||||
"""
|
||||
request = http_request._copy()
|
||||
_scrub_request(request)
|
||||
if self.real_client is None:
|
||||
self.last_request_was_live = False
|
||||
for recording in self._recordings:
|
||||
if _match_request(recording[0], request):
|
||||
return recording[1]
|
||||
else:
|
||||
# Pass along the debug settings to the real client.
|
||||
self.real_client.debug = self.debug
|
||||
# Make an actual request since we can use the real HTTP client.
|
||||
self.last_request_was_live = True
|
||||
response = self.real_client.request(http_request)
|
||||
scrubbed_response = _scrub_response(response)
|
||||
self.add_response(request, scrubbed_response.status,
|
||||
scrubbed_response.reason,
|
||||
dict(atom.http_core.get_headers(scrubbed_response)),
|
||||
scrubbed_response.read())
|
||||
# Return the recording which we just added.
|
||||
return self._recordings[-1][1]
|
||||
raise NoRecordingFound('No recoding was found for request: %s %s' % (
|
||||
request.method, str(request.uri)))
|
||||
|
||||
Request = request
|
||||
|
||||
def _save_recordings(self, filename):
|
||||
recording_file = open(os.path.join(tempfile.gettempdir(), filename),
|
||||
'wb')
|
||||
pickle.dump(self._recordings, recording_file)
|
||||
recording_file.close()
|
||||
|
||||
def _load_recordings(self, filename):
|
||||
recording_file = open(os.path.join(tempfile.gettempdir(), filename),
|
||||
'rb')
|
||||
self._recordings = pickle.load(recording_file)
|
||||
recording_file.close()
|
||||
|
||||
def _delete_recordings(self, filename):
|
||||
full_path = os.path.join(tempfile.gettempdir(), filename)
|
||||
if os.path.exists(full_path):
|
||||
os.remove(full_path)
|
||||
|
||||
def _load_or_use_client(self, filename, http_client):
|
||||
if os.path.exists(os.path.join(tempfile.gettempdir(), filename)):
|
||||
self._load_recordings(filename)
|
||||
else:
|
||||
self.real_client = http_client
|
||||
|
||||
def use_cached_session(self, name=None, real_http_client=None):
|
||||
"""Attempts to load recordings from a previous live request.
|
||||
|
||||
If a temp file with the recordings exists, then it is used to fulfill
|
||||
requests. If the file does not exist, then a real client is used to
|
||||
actually make the desired HTTP requests. Requests and responses are
|
||||
recorded and will be written to the desired temprary cache file when
|
||||
close_session is called.
|
||||
|
||||
Args:
|
||||
name: str (optional) The file name of session file to be used. The file
|
||||
is loaded from the temporary directory of this machine. If no name
|
||||
is passed in, a default name will be constructed using the
|
||||
cache_name_prefix, cache_case_name, and cache_test_name of this
|
||||
object.
|
||||
real_http_client: atom.http_core.HttpClient the real client to be used
|
||||
if the cached recordings are not found. If the default
|
||||
value is used, this will be an
|
||||
atom.http_core.HttpClient.
|
||||
"""
|
||||
if real_http_client is None:
|
||||
real_http_client = atom.http_core.HttpClient()
|
||||
if name is None:
|
||||
self._recordings_cache_name = self.get_cache_file_name()
|
||||
else:
|
||||
self._recordings_cache_name = name
|
||||
self._load_or_use_client(self._recordings_cache_name, real_http_client)
|
||||
|
||||
def close_session(self):
|
||||
"""Saves recordings in the temporary file named in use_cached_session."""
|
||||
if self.real_client is not None:
|
||||
self._save_recordings(self._recordings_cache_name)
|
||||
|
||||
def delete_session(self, name=None):
|
||||
"""Removes recordings from a previous live request."""
|
||||
if name is None:
|
||||
self._delete_recordings(self._recordings_cache_name)
|
||||
else:
|
||||
self._delete_recordings(name)
|
||||
|
||||
def get_cache_file_name(self):
|
||||
return '%s.%s.%s' % (self.cache_name_prefix, self.cache_case_name,
|
||||
self.cache_test_name)
|
||||
|
||||
def _dump(self):
|
||||
"""Provides debug information in a string."""
|
||||
output = 'MockHttpClient\n real_client: %s\n cache file name: %s\n' % (
|
||||
self.real_client, self.get_cache_file_name())
|
||||
output += ' recordings:\n'
|
||||
i = 0
|
||||
for recording in self._recordings:
|
||||
output += ' recording %i is for: %s %s\n' % (
|
||||
i, recording[0].method, str(recording[0].uri))
|
||||
i += 1
|
||||
return output
|
||||
|
||||
|
||||
def _match_request(http_request, stored_request):
|
||||
"""Determines whether a request is similar enough to a stored request
|
||||
to cause the stored response to be returned."""
|
||||
# Check to see if the host names match.
|
||||
if (http_request.uri.host is not None
|
||||
and http_request.uri.host != stored_request.uri.host):
|
||||
return False
|
||||
# Check the request path in the URL (/feeds/private/full/x)
|
||||
elif http_request.uri.path != stored_request.uri.path:
|
||||
return False
|
||||
# Check the method used in the request (GET, POST, etc.)
|
||||
elif http_request.method != stored_request.method:
|
||||
return False
|
||||
# If there is a gsession ID in either request, make sure that it is matched
|
||||
# exactly.
|
||||
elif ('gsessionid' in http_request.uri.query
|
||||
or 'gsessionid' in stored_request.uri.query):
|
||||
if 'gsessionid' not in stored_request.uri.query:
|
||||
return False
|
||||
elif 'gsessionid' not in http_request.uri.query:
|
||||
return False
|
||||
elif (http_request.uri.query['gsessionid']
|
||||
!= stored_request.uri.query['gsessionid']):
|
||||
return False
|
||||
# Ignores differences in the query params (?start-index=5&max-results=20),
|
||||
# the body of the request, the port number, HTTP headers, just to name a
|
||||
# few.
|
||||
return True
|
||||
|
||||
|
||||
def _scrub_request(http_request):
|
||||
""" Removes email address and password from a client login request.
|
||||
|
||||
Since the mock server saves the request and response in plantext, sensitive
|
||||
information like the password should be removed before saving the
|
||||
recordings. At the moment only requests sent to a ClientLogin url are
|
||||
scrubbed.
|
||||
"""
|
||||
if (http_request and http_request.uri and http_request.uri.path and
|
||||
http_request.uri.path.endswith('ClientLogin')):
|
||||
# Remove the email and password from a ClientLogin request.
|
||||
http_request._body_parts = []
|
||||
http_request.add_form_inputs(
|
||||
{'form_data': 'client login request has been scrubbed'})
|
||||
else:
|
||||
# We can remove the body of the post from the recorded request, since
|
||||
# the request body is not used when finding a matching recording.
|
||||
http_request._body_parts = []
|
||||
return http_request
|
||||
|
||||
|
||||
def _scrub_response(http_response):
|
||||
return http_response
|
||||
|
||||
|
||||
class EchoHttpClient(object):
|
||||
"""Sends the request data back in the response.
|
||||
|
||||
Used to check the formatting of the request as it was sent. Always responds
|
||||
with a 200 OK, and some information from the HTTP request is returned in
|
||||
special Echo-X headers in the response. The following headers are added
|
||||
in the response:
|
||||
'Echo-Host': The host name and port number to which the HTTP connection is
|
||||
made. If no port was passed in, the header will contain
|
||||
host:None.
|
||||
'Echo-Uri': The path portion of the URL being requested. /example?x=1&y=2
|
||||
'Echo-Scheme': The beginning of the URL, usually 'http' or 'https'
|
||||
'Echo-Method': The HTTP method being used, 'GET', 'POST', 'PUT', etc.
|
||||
"""
|
||||
|
||||
def request(self, http_request):
|
||||
return self._http_request(http_request.uri, http_request.method,
|
||||
http_request.headers, http_request._body_parts)
|
||||
|
||||
def _http_request(self, uri, method, headers=None, body_parts=None):
|
||||
body = io.StringIO()
|
||||
response = atom.http_core.HttpResponse(status=200, reason='OK', body=body)
|
||||
if headers is None:
|
||||
response._headers = {}
|
||||
else:
|
||||
# Copy headers from the request to the response but convert values to
|
||||
# strings. Server response headers always come in as strings, so an int
|
||||
# should be converted to a corresponding string when echoing.
|
||||
for header, value in headers.items():
|
||||
response._headers[header] = str(value)
|
||||
response._headers['Echo-Host'] = '%s:%s' % (uri.host, str(uri.port))
|
||||
response._headers['Echo-Uri'] = uri._get_relative_path()
|
||||
response._headers['Echo-Scheme'] = uri.scheme
|
||||
response._headers['Echo-Method'] = method
|
||||
for part in body_parts:
|
||||
if isinstance(part, str):
|
||||
body.write(part)
|
||||
elif hasattr(part, 'read'):
|
||||
body.write(part.read())
|
||||
body.seek(0)
|
||||
return response
|
||||
|
||||
|
||||
class SettableHttpClient(object):
|
||||
"""An HTTP Client which responds with the data given in set_response."""
|
||||
|
||||
def __init__(self, status, reason, body, headers):
|
||||
"""Configures the response for the server.
|
||||
|
||||
See set_response for details on the arguments to the constructor.
|
||||
"""
|
||||
self.set_response(status, reason, body, headers)
|
||||
self.last_request = None
|
||||
|
||||
def set_response(self, status, reason, body, headers):
|
||||
"""Determines the response which will be sent for each request.
|
||||
|
||||
Args:
|
||||
status: An int for the HTTP status code, example: 200, 404, etc.
|
||||
reason: String for the HTTP reason, example: OK, NOT FOUND, etc.
|
||||
body: The body of the HTTP response as a string or a file-like
|
||||
object (something with a read method).
|
||||
headers: dict of strings containing the HTTP headers in the response.
|
||||
"""
|
||||
self.response = atom.http_core.HttpResponse(status=status, reason=reason,
|
||||
body=body)
|
||||
self.response._headers = headers.copy()
|
||||
|
||||
def request(self, http_request):
|
||||
self.last_request = http_request
|
||||
return self.response
|
||||
|
||||
|
||||
class MockHttpResponse(atom.http_core.HttpResponse):
|
||||
def __init__(self, status=None, reason=None, headers=None, body=None):
|
||||
self._headers = headers or {}
|
||||
if status is not None:
|
||||
self.status = status
|
||||
if reason is not None:
|
||||
self.reason = reason
|
||||
if body is not None:
|
||||
# Instead of using a file-like object for the body, store as a string
|
||||
# so that reads can be repeated.
|
||||
if hasattr(body, 'read'):
|
||||
self._body = body.read()
|
||||
else:
|
||||
self._body = body
|
||||
|
||||
def read(self):
|
||||
return self._body
|
||||
@@ -1,235 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2008 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License 2.0;
|
||||
|
||||
|
||||
|
||||
"""MockService provides CRUD ops. for mocking calls to AtomPub services.
|
||||
|
||||
MockService: Exposes the publicly used methods of AtomService to provide
|
||||
a mock interface which can be used in unit tests.
|
||||
"""
|
||||
|
||||
import pickle
|
||||
|
||||
import atom.service
|
||||
|
||||
# __author__ = 'api.jscudder (Jeffrey Scudder)'
|
||||
|
||||
# Recordings contains pairings of HTTP MockRequest objects with MockHttpResponse objects.
|
||||
recordings = []
|
||||
# If set, the mock service HttpRequest are actually made through this object.
|
||||
real_request_handler = None
|
||||
|
||||
|
||||
def ConcealValueWithSha(source):
|
||||
import sha
|
||||
return sha.new(source[:-5]).hexdigest()
|
||||
|
||||
|
||||
def DumpRecordings(conceal_func=ConcealValueWithSha):
|
||||
if conceal_func:
|
||||
for recording_pair in recordings:
|
||||
recording_pair[0].ConcealSecrets(conceal_func)
|
||||
return pickle.dumps(recordings)
|
||||
|
||||
|
||||
def LoadRecordings(recordings_file_or_string):
|
||||
if isinstance(recordings_file_or_string, str):
|
||||
atom.mock_service.recordings = pickle.loads(recordings_file_or_string)
|
||||
elif hasattr(recordings_file_or_string, 'read'):
|
||||
atom.mock_service.recordings = pickle.loads(
|
||||
recordings_file_or_string.read())
|
||||
|
||||
|
||||
def HttpRequest(service, operation, data, uri, extra_headers=None,
|
||||
url_params=None, escape_params=True, content_type='application/atom+xml'):
|
||||
"""Simulates an HTTP call to the server, makes an actual HTTP request if
|
||||
real_request_handler is set.
|
||||
|
||||
This function operates in two different modes depending on if
|
||||
real_request_handler is set or not. If real_request_handler is not set,
|
||||
HttpRequest will look in this module's recordings list to find a response
|
||||
which matches the parameters in the function call. If real_request_handler
|
||||
is set, this function will call real_request_handler.HttpRequest, add the
|
||||
response to the recordings list, and respond with the actual response.
|
||||
|
||||
Args:
|
||||
service: atom.AtomService object which contains some of the parameters
|
||||
needed to make the request. The following members are used to
|
||||
construct the HTTP call: server (str), additional_headers (dict),
|
||||
port (int), and ssl (bool).
|
||||
operation: str The HTTP operation to be performed. This is usually one of
|
||||
'GET', 'POST', 'PUT', or 'DELETE'
|
||||
data: ElementTree, filestream, list of parts, or other object which can be
|
||||
converted to a string.
|
||||
Should be set to None when performing a GET or PUT.
|
||||
If data is a file-like object which can be read, this method will read
|
||||
a chunk of 100K bytes at a time and send them.
|
||||
If the data is a list of parts to be sent, each part will be evaluated
|
||||
and sent.
|
||||
uri: The beginning of the URL to which the request should be sent.
|
||||
Examples: '/', '/base/feeds/snippets',
|
||||
'/m8/feeds/contacts/default/base'
|
||||
extra_headers: dict of strings. HTTP headers which should be sent
|
||||
in the request. These headers are in addition to those stored in
|
||||
service.additional_headers.
|
||||
url_params: dict of strings. Key value pairs to be added to the URL as
|
||||
URL parameters. For example {'foo':'bar', 'test':'param'} will
|
||||
become ?foo=bar&test=param.
|
||||
escape_params: bool default True. If true, the keys and values in
|
||||
url_params will be URL escaped when the form is constructed
|
||||
(Special characters converted to %XX form.)
|
||||
content_type: str The MIME type for the data being sent. Defaults to
|
||||
'application/atom+xml', this is only used if data is set.
|
||||
"""
|
||||
full_uri = atom.service.BuildUri(uri, url_params, escape_params)
|
||||
(server, port, ssl, uri) = atom.service.ProcessUrl(service, uri)
|
||||
current_request = MockRequest(operation, full_uri, host=server, ssl=ssl,
|
||||
data=data, extra_headers=extra_headers, url_params=url_params,
|
||||
escape_params=escape_params, content_type=content_type)
|
||||
# If the request handler is set, we should actually make the request using
|
||||
# the request handler and record the response to replay later.
|
||||
if real_request_handler:
|
||||
response = real_request_handler.HttpRequest(service, operation, data, uri,
|
||||
extra_headers=extra_headers, url_params=url_params,
|
||||
escape_params=escape_params, content_type=content_type)
|
||||
# TODO: need to copy the HTTP headers from the real response into the
|
||||
# recorded_response.
|
||||
recorded_response = MockHttpResponse(body=response.read(),
|
||||
status=response.status, reason=response.reason)
|
||||
# Insert a tuple which maps the request to the response object returned
|
||||
# when making an HTTP call using the real_request_handler.
|
||||
recordings.append((current_request, recorded_response))
|
||||
return recorded_response
|
||||
else:
|
||||
# Look through available recordings to see if one matches the current
|
||||
# request.
|
||||
for request_response_pair in recordings:
|
||||
if request_response_pair[0].IsMatch(current_request):
|
||||
return request_response_pair[1]
|
||||
return None
|
||||
|
||||
|
||||
class MockRequest(object):
|
||||
"""Represents a request made to an AtomPub server.
|
||||
|
||||
These objects are used to determine if a client request matches a recorded
|
||||
HTTP request to determine what the mock server's response will be.
|
||||
"""
|
||||
|
||||
def __init__(self, operation, uri, host=None, ssl=False, port=None,
|
||||
data=None, extra_headers=None, url_params=None, escape_params=True,
|
||||
content_type='application/atom+xml'):
|
||||
"""Constructor for a MockRequest
|
||||
|
||||
Args:
|
||||
operation: str One of 'GET', 'POST', 'PUT', or 'DELETE' this is the
|
||||
HTTP operation requested on the resource.
|
||||
uri: str The URL describing the resource to be modified or feed to be
|
||||
retrieved. This should include the protocol (http/https) and the host
|
||||
(aka domain). For example, these are some valud full_uris:
|
||||
'http://example.com', 'https://www.google.com/accounts/ClientLogin'
|
||||
host: str (optional) The server name which will be placed at the
|
||||
beginning of the URL if the uri parameter does not begin with 'http'.
|
||||
Examples include 'example.com', 'www.google.com', 'www.blogger.com'.
|
||||
ssl: boolean (optional) If true, the request URL will begin with https
|
||||
instead of http.
|
||||
data: ElementTree, filestream, list of parts, or other object which can be
|
||||
converted to a string. (optional)
|
||||
Should be set to None when performing a GET or PUT.
|
||||
If data is a file-like object which can be read, the constructor
|
||||
will read the entire file into memory. If the data is a list of
|
||||
parts to be sent, each part will be evaluated and stored.
|
||||
extra_headers: dict (optional) HTTP headers included in the request.
|
||||
url_params: dict (optional) Key value pairs which should be added to
|
||||
the URL as URL parameters in the request. For example uri='/',
|
||||
url_parameters={'foo':'1','bar':'2'} could become '/?foo=1&bar=2'.
|
||||
escape_params: boolean (optional) Perform URL escaping on the keys and
|
||||
values specified in url_params. Defaults to True.
|
||||
content_type: str (optional) Provides the MIME type of the data being
|
||||
sent.
|
||||
"""
|
||||
self.operation = operation
|
||||
self.uri = _ConstructFullUrlBase(uri, host=host, ssl=ssl)
|
||||
self.data = data
|
||||
self.extra_headers = extra_headers
|
||||
self.url_params = url_params or {}
|
||||
self.escape_params = escape_params
|
||||
self.content_type = content_type
|
||||
|
||||
def ConcealSecrets(self, conceal_func):
|
||||
"""Conceal secret data in this request."""
|
||||
if 'Authorization' in self.extra_headers:
|
||||
self.extra_headers['Authorization'] = conceal_func(
|
||||
self.extra_headers['Authorization'])
|
||||
|
||||
def IsMatch(self, other_request):
|
||||
"""Check to see if the other_request is equivalent to this request.
|
||||
|
||||
Used to determine if a recording matches an incoming request so that a
|
||||
recorded response should be sent to the client.
|
||||
|
||||
The matching is not exact, only the operation and URL are examined
|
||||
currently.
|
||||
|
||||
Args:
|
||||
other_request: MockRequest The request which we want to check this
|
||||
(self) MockRequest against to see if they are equivalent.
|
||||
"""
|
||||
# More accurate matching logic will likely be required.
|
||||
return (self.operation == other_request.operation and self.uri ==
|
||||
other_request.uri)
|
||||
|
||||
|
||||
def _ConstructFullUrlBase(uri, host=None, ssl=False):
|
||||
"""Puts URL components into the form http(s)://full.host.strinf/uri/path
|
||||
|
||||
Used to construct a roughly canonical URL so that URLs which begin with
|
||||
'http://example.com/' can be compared to a uri of '/' when the host is
|
||||
set to 'example.com'
|
||||
|
||||
If the uri contains 'http://host' already, the host and ssl parameters
|
||||
are ignored.
|
||||
|
||||
Args:
|
||||
uri: str The path component of the URL, examples include '/'
|
||||
host: str (optional) The host name which should prepend the URL. Example:
|
||||
'example.com'
|
||||
ssl: boolean (optional) If true, the returned URL will begin with https
|
||||
instead of http.
|
||||
|
||||
Returns:
|
||||
String which has the form http(s)://example.com/uri/string/contents
|
||||
"""
|
||||
if uri.startswith('http'):
|
||||
return uri
|
||||
if ssl:
|
||||
return 'https://%s%s' % (host, uri)
|
||||
else:
|
||||
return 'http://%s%s' % (host, uri)
|
||||
|
||||
|
||||
class MockHttpResponse(object):
|
||||
"""Returned from MockService crud methods as the server's response."""
|
||||
|
||||
def __init__(self, body=None, status=None, reason=None, headers=None):
|
||||
"""Construct a mock HTTPResponse and set members.
|
||||
|
||||
Args:
|
||||
body: str (optional) The HTTP body of the server's response.
|
||||
status: int (optional)
|
||||
reason: str (optional)
|
||||
headers: dict (optional)
|
||||
"""
|
||||
self.body = body
|
||||
self.status = status
|
||||
self.reason = reason
|
||||
self.headers = headers or {}
|
||||
|
||||
def read(self):
|
||||
return self.body
|
||||
|
||||
def getheader(self, header_name):
|
||||
return self.headers[header_name]
|
||||
@@ -1,723 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2006, 2007, 2008 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License 2.0;
|
||||
|
||||
|
||||
|
||||
"""AtomService provides CRUD ops. in line with the Atom Publishing Protocol.
|
||||
|
||||
AtomService: Encapsulates the ability to perform insert, update and delete
|
||||
operations with the Atom Publishing Protocol on which GData is
|
||||
based. An instance can perform query, insertion, deletion, and
|
||||
update.
|
||||
|
||||
HttpRequest: Function that performs a GET, POST, PUT, or DELETE HTTP request
|
||||
to the specified end point. An AtomService object or a subclass can be
|
||||
used to specify information about the request.
|
||||
"""
|
||||
|
||||
# __author__ = 'api.jscudder (Jeff Scudder)'
|
||||
|
||||
import base64
|
||||
import http.client
|
||||
import os
|
||||
import socket
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import warnings
|
||||
|
||||
import atom.http
|
||||
import atom.http_interface
|
||||
import atom.token_store
|
||||
import atom.url
|
||||
|
||||
import lxml.etree as ElementTree
|
||||
import atom
|
||||
|
||||
|
||||
class AtomService(object):
|
||||
"""Performs Atom Publishing Protocol CRUD operations.
|
||||
|
||||
The AtomService contains methods to perform HTTP CRUD operations.
|
||||
"""
|
||||
|
||||
# Default values for members
|
||||
port = 80
|
||||
ssl = False
|
||||
# Set the current_token to force the AtomService to use this token
|
||||
# instead of searching for an appropriate token in the token_store.
|
||||
current_token = None
|
||||
auto_store_tokens = True
|
||||
auto_set_current_token = True
|
||||
|
||||
def _get_override_token(self):
|
||||
return self.current_token
|
||||
|
||||
def _set_override_token(self, token):
|
||||
self.current_token = token
|
||||
|
||||
override_token = property(_get_override_token, _set_override_token)
|
||||
|
||||
# @atom.v1_deprecated('Please use atom.client.AtomPubClient instead.')
|
||||
def __init__(self, server=None, additional_headers=None,
|
||||
application_name='', http_client=None, token_store=None):
|
||||
"""Creates a new AtomService client.
|
||||
|
||||
Args:
|
||||
server: string (optional) The start of a URL for the server
|
||||
to which all operations should be directed. Example:
|
||||
'www.google.com'
|
||||
additional_headers: dict (optional) Any additional HTTP headers which
|
||||
should be included with CRUD operations.
|
||||
http_client: An object responsible for making HTTP requests using a
|
||||
request method. If none is provided, a new instance of
|
||||
atom.http.ProxiedHttpClient will be used.
|
||||
token_store: Keeps a collection of authorization tokens which can be
|
||||
applied to requests for a specific URLs. Critical methods are
|
||||
find_token based on a URL (atom.url.Url or a string), add_token,
|
||||
and remove_token.
|
||||
"""
|
||||
self.http_client = http_client or atom.http.ProxiedHttpClient()
|
||||
self.token_store = token_store or atom.token_store.TokenStore()
|
||||
self.server = server
|
||||
self.additional_headers = additional_headers or {}
|
||||
self.additional_headers['User-Agent'] = atom.http_interface.USER_AGENT % (
|
||||
application_name,)
|
||||
# If debug is True, the HTTPConnection will display debug information
|
||||
self._set_debug(False)
|
||||
|
||||
__init__ = atom.v1_deprecated(
|
||||
'Please use atom.client.AtomPubClient instead.')(
|
||||
__init__)
|
||||
|
||||
def _get_debug(self):
|
||||
return self.http_client.debug
|
||||
|
||||
def _set_debug(self, value):
|
||||
self.http_client.debug = value
|
||||
|
||||
debug = property(_get_debug, _set_debug,
|
||||
doc='If True, HTTP debug information is printed.')
|
||||
|
||||
def use_basic_auth(self, username, password, scopes=None):
|
||||
if username is not None and password is not None:
|
||||
if scopes is None:
|
||||
scopes = [atom.token_store.SCOPE_ALL]
|
||||
base_64_string = base64.encodestring('%s:%s' % (username, password))
|
||||
token = BasicAuthToken('Basic %s' % base_64_string.strip(),
|
||||
scopes=[atom.token_store.SCOPE_ALL])
|
||||
if self.auto_set_current_token:
|
||||
self.current_token = token
|
||||
if self.auto_store_tokens:
|
||||
return self.token_store.add_token(token)
|
||||
return True
|
||||
return False
|
||||
|
||||
def UseBasicAuth(self, username, password, for_proxy=False):
|
||||
"""Sets an Authenticaiton: Basic HTTP header containing plaintext.
|
||||
|
||||
Deprecated, use use_basic_auth instead.
|
||||
|
||||
The username and password are base64 encoded and added to an HTTP header
|
||||
which will be included in each request. Note that your username and
|
||||
password are sent in plaintext.
|
||||
|
||||
Args:
|
||||
username: str
|
||||
password: str
|
||||
"""
|
||||
self.use_basic_auth(username, password)
|
||||
|
||||
# @atom.v1_deprecated('Please use atom.client.AtomPubClient for requests.')
|
||||
def request(self, operation, url, data=None, headers=None,
|
||||
url_params=None):
|
||||
if isinstance(url, str):
|
||||
if url.startswith('http:') and self.ssl:
|
||||
# Force all requests to be https if self.ssl is True.
|
||||
url = atom.url.parse_url('https:' + url[5:])
|
||||
elif not url.startswith('http') and self.ssl:
|
||||
url = atom.url.parse_url('https://%s%s' % (self.server, url))
|
||||
elif not url.startswith('http'):
|
||||
url = atom.url.parse_url('http://%s%s' % (self.server, url))
|
||||
else:
|
||||
url = atom.url.parse_url(url)
|
||||
|
||||
if url_params:
|
||||
for name, value in url_params.items():
|
||||
url.params[name] = value
|
||||
|
||||
all_headers = self.additional_headers.copy()
|
||||
if headers:
|
||||
all_headers.update(headers)
|
||||
|
||||
# If the list of headers does not include a Content-Length, attempt to
|
||||
# calculate it based on the data object.
|
||||
if data and 'Content-Length' not in all_headers:
|
||||
content_length = CalculateDataLength(data)
|
||||
if content_length:
|
||||
all_headers['Content-Length'] = str(content_length)
|
||||
|
||||
# Find an Authorization token for this URL if one is available.
|
||||
if self.override_token:
|
||||
auth_token = self.override_token
|
||||
else:
|
||||
auth_token = self.token_store.find_token(url)
|
||||
return auth_token.perform_request(self.http_client, operation, url,
|
||||
data=data, headers=all_headers)
|
||||
|
||||
request = atom.v1_deprecated(
|
||||
'Please use atom.client.AtomPubClient for requests.')(
|
||||
request)
|
||||
|
||||
# CRUD operations
|
||||
def Get(self, uri, extra_headers=None, url_params=None, escape_params=True):
|
||||
"""Query the APP server with the given URI
|
||||
|
||||
The uri is the portion of the URI after the server value
|
||||
(server example: 'www.google.com').
|
||||
|
||||
Example use:
|
||||
To perform a query against Google Base, set the server to
|
||||
'base.google.com' and set the uri to '/base/feeds/...', where ... is
|
||||
your query. For example, to find snippets for all digital cameras uri
|
||||
should be set to: '/base/feeds/snippets?bq=digital+camera'
|
||||
|
||||
Args:
|
||||
uri: string The query in the form of a URI. Example:
|
||||
'/base/feeds/snippets?bq=digital+camera'.
|
||||
extra_headers: dicty (optional) Extra HTTP headers to be included
|
||||
in the GET request. These headers are in addition to
|
||||
those stored in the client's additional_headers property.
|
||||
The client automatically sets the Content-Type and
|
||||
Authorization headers.
|
||||
url_params: dict (optional) Additional URL parameters to be included
|
||||
in the query. These are translated into query arguments
|
||||
in the form '&dict_key=value&...'.
|
||||
Example: {'max-results': '250'} becomes &max-results=250
|
||||
escape_params: boolean (optional) If false, the calling code has already
|
||||
ensured that the query will form a valid URL (all
|
||||
reserved characters have been escaped). If true, this
|
||||
method will escape the query and any URL parameters
|
||||
provided.
|
||||
|
||||
Returns:
|
||||
httplib.HTTPResponse The server's response to the GET request.
|
||||
"""
|
||||
return self.request('GET', uri, data=None, headers=extra_headers,
|
||||
url_params=url_params)
|
||||
|
||||
def Post(self, data, uri, extra_headers=None, url_params=None,
|
||||
escape_params=True, content_type='application/atom+xml'):
|
||||
"""Insert data into an APP server at the given URI.
|
||||
|
||||
Args:
|
||||
data: string, ElementTree._Element, or something with a __str__ method
|
||||
The XML to be sent to the uri.
|
||||
uri: string The location (feed) to which the data should be inserted.
|
||||
Example: '/base/feeds/items'.
|
||||
extra_headers: dict (optional) HTTP headers which are to be included.
|
||||
The client automatically sets the Content-Type,
|
||||
Authorization, and Content-Length headers.
|
||||
url_params: dict (optional) Additional URL parameters to be included
|
||||
in the URI. These are translated into query arguments
|
||||
in the form '&dict_key=value&...'.
|
||||
Example: {'max-results': '250'} becomes &max-results=250
|
||||
escape_params: boolean (optional) If false, the calling code has already
|
||||
ensured that the query will form a valid URL (all
|
||||
reserved characters have been escaped). If true, this
|
||||
method will escape the query and any URL parameters
|
||||
provided.
|
||||
|
||||
Returns:
|
||||
httplib.HTTPResponse Server's response to the POST request.
|
||||
"""
|
||||
if extra_headers is None:
|
||||
extra_headers = {}
|
||||
if content_type:
|
||||
extra_headers['Content-Type'] = content_type
|
||||
return self.request('POST', uri, data=data, headers=extra_headers,
|
||||
url_params=url_params)
|
||||
|
||||
def Put(self, data, uri, extra_headers=None, url_params=None,
|
||||
escape_params=True, content_type='application/atom+xml'):
|
||||
"""Updates an entry at the given URI.
|
||||
|
||||
Args:
|
||||
data: string, ElementTree._Element, or xml_wrapper.ElementWrapper The
|
||||
XML containing the updated data.
|
||||
uri: string A URI indicating entry to which the update will be applied.
|
||||
Example: '/base/feeds/items/ITEM-ID'
|
||||
extra_headers: dict (optional) HTTP headers which are to be included.
|
||||
The client automatically sets the Content-Type,
|
||||
Authorization, and Content-Length headers.
|
||||
url_params: dict (optional) Additional URL parameters to be included
|
||||
in the URI. These are translated into query arguments
|
||||
in the form '&dict_key=value&...'.
|
||||
Example: {'max-results': '250'} becomes &max-results=250
|
||||
escape_params: boolean (optional) If false, the calling code has already
|
||||
ensured that the query will form a valid URL (all
|
||||
reserved characters have been escaped). If true, this
|
||||
method will escape the query and any URL parameters
|
||||
provided.
|
||||
|
||||
Returns:
|
||||
httplib.HTTPResponse Server's response to the PUT request.
|
||||
"""
|
||||
if extra_headers is None:
|
||||
extra_headers = {}
|
||||
if content_type:
|
||||
extra_headers['Content-Type'] = content_type
|
||||
return self.request('PUT', uri, data=data, headers=extra_headers,
|
||||
url_params=url_params)
|
||||
|
||||
def Delete(self, uri, extra_headers=None, url_params=None,
|
||||
escape_params=True):
|
||||
"""Deletes the entry at the given URI.
|
||||
|
||||
Args:
|
||||
uri: string The URI of the entry to be deleted. Example:
|
||||
'/base/feeds/items/ITEM-ID'
|
||||
extra_headers: dict (optional) HTTP headers which are to be included.
|
||||
The client automatically sets the Content-Type and
|
||||
Authorization headers.
|
||||
url_params: dict (optional) Additional URL parameters to be included
|
||||
in the URI. These are translated into query arguments
|
||||
in the form '&dict_key=value&...'.
|
||||
Example: {'max-results': '250'} becomes &max-results=250
|
||||
escape_params: boolean (optional) If false, the calling code has already
|
||||
ensured that the query will form a valid URL (all
|
||||
reserved characters have been escaped). If true, this
|
||||
method will escape the query and any URL parameters
|
||||
provided.
|
||||
|
||||
Returns:
|
||||
httplib.HTTPResponse Server's response to the DELETE request.
|
||||
"""
|
||||
return self.request('DELETE', uri, data=None, headers=extra_headers,
|
||||
url_params=url_params)
|
||||
|
||||
|
||||
class BasicAuthToken(atom.http_interface.GenericToken):
|
||||
def __init__(self, auth_header, scopes=None):
|
||||
"""Creates a token used to add Basic Auth headers to HTTP requests.
|
||||
|
||||
Args:
|
||||
auth_header: str The value for the Authorization header.
|
||||
scopes: list of str or atom.url.Url specifying the beginnings of URLs
|
||||
for which this token can be used. For example, if scopes contains
|
||||
'http://example.com/foo', then this token can be used for a request to
|
||||
'http://example.com/foo/bar' but it cannot be used for a request to
|
||||
'http://example.com/baz'
|
||||
"""
|
||||
self.auth_header = auth_header
|
||||
self.scopes = scopes or []
|
||||
|
||||
def perform_request(self, http_client, operation, url, data=None,
|
||||
headers=None):
|
||||
"""Sets the Authorization header to the basic auth string."""
|
||||
if headers is None:
|
||||
headers = {'Authorization': self.auth_header}
|
||||
else:
|
||||
headers['Authorization'] = self.auth_header
|
||||
return http_client.request(operation, url, data=data, headers=headers)
|
||||
|
||||
def __str__(self):
|
||||
return self.auth_header
|
||||
|
||||
def valid_for_scope(self, url):
|
||||
"""Tells the caller if the token authorizes access to the desired URL.
|
||||
"""
|
||||
if isinstance(url, str):
|
||||
url = atom.url.parse_url(url)
|
||||
for scope in self.scopes:
|
||||
if scope == atom.token_store.SCOPE_ALL:
|
||||
return True
|
||||
if isinstance(scope, str):
|
||||
scope = atom.url.parse_url(scope)
|
||||
if scope == url:
|
||||
return True
|
||||
# Check the host and the path, but ignore the port and protocol.
|
||||
elif scope.host == url.host and not scope.path:
|
||||
return True
|
||||
elif scope.host == url.host and scope.path and not url.path:
|
||||
continue
|
||||
elif scope.host == url.host and url.path.startswith(scope.path):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def PrepareConnection(service, full_uri):
|
||||
"""Opens a connection to the server based on the full URI.
|
||||
|
||||
This method is deprecated, instead use atom.http.HttpClient.request.
|
||||
|
||||
Examines the target URI and the proxy settings, which are set as
|
||||
environment variables, to open a connection with the server. This
|
||||
connection is used to make an HTTP request.
|
||||
|
||||
Args:
|
||||
service: atom.AtomService or a subclass. It must have a server string which
|
||||
represents the server host to which the request should be made. It may also
|
||||
have a dictionary of additional_headers to send in the HTTP request.
|
||||
full_uri: str Which is the target relative (lacks protocol and host) or
|
||||
absolute URL to be opened. Example:
|
||||
'https://www.google.com/accounts/ClientLogin' or
|
||||
'base/feeds/snippets' where the server is set to www.google.com.
|
||||
|
||||
Returns:
|
||||
A tuple containing the httplib.HTTPConnection and the full_uri for the
|
||||
request.
|
||||
"""
|
||||
deprecation('calling deprecated function PrepareConnection')
|
||||
(server, port, ssl, partial_uri) = ProcessUrl(service, full_uri)
|
||||
if ssl:
|
||||
# destination is https
|
||||
proxy = os.environ.get('https_proxy')
|
||||
if proxy:
|
||||
(p_server, p_port, p_ssl, p_uri) = ProcessUrl(service, proxy, True)
|
||||
proxy_username = os.environ.get('proxy-username')
|
||||
if not proxy_username:
|
||||
proxy_username = os.environ.get('proxy_username')
|
||||
proxy_password = os.environ.get('proxy-password')
|
||||
if not proxy_password:
|
||||
proxy_password = os.environ.get('proxy_password')
|
||||
if proxy_username:
|
||||
user_auth = base64.encodestring('%s:%s' % (proxy_username,
|
||||
proxy_password))
|
||||
proxy_authorization = ('Proxy-authorization: Basic %s\r\n' % (
|
||||
user_auth.strip()))
|
||||
else:
|
||||
proxy_authorization = ''
|
||||
proxy_connect = 'CONNECT %s:%s HTTP/1.0\r\n' % (server, port)
|
||||
user_agent = 'User-Agent: %s\r\n' % (
|
||||
service.additional_headers['User-Agent'])
|
||||
proxy_pieces = (proxy_connect + proxy_authorization + user_agent
|
||||
+ '\r\n')
|
||||
|
||||
# now connect, very simple recv and error checking
|
||||
p_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
p_sock.connect((p_server, p_port))
|
||||
p_sock.sendall(proxy_pieces)
|
||||
response = ''
|
||||
|
||||
# Wait for the full response.
|
||||
while response.find("\r\n\r\n") == -1:
|
||||
response += p_sock.recv(8192)
|
||||
|
||||
p_status = response.split()[1]
|
||||
if p_status != str(200):
|
||||
raise atom.http.ProxyError('Error status=%s' % p_status)
|
||||
|
||||
# Trivial setup for ssl socket.
|
||||
ssl = socket.ssl(p_sock, None, None)
|
||||
fake_sock = http.client.FakeSocket(p_sock, ssl)
|
||||
|
||||
# Initalize httplib and replace with the proxy socket.
|
||||
connection = http.client.HTTPConnection(server)
|
||||
connection.sock = fake_sock
|
||||
full_uri = partial_uri
|
||||
|
||||
else:
|
||||
connection = http.client.HTTPSConnection(server, port)
|
||||
full_uri = partial_uri
|
||||
|
||||
else:
|
||||
# destination is http
|
||||
proxy = os.environ.get('http_proxy')
|
||||
if proxy:
|
||||
(p_server, p_port, p_ssl, p_uri) = ProcessUrl(service.server, proxy, True)
|
||||
proxy_username = os.environ.get('proxy-username')
|
||||
if not proxy_username:
|
||||
proxy_username = os.environ.get('proxy_username')
|
||||
proxy_password = os.environ.get('proxy-password')
|
||||
if not proxy_password:
|
||||
proxy_password = os.environ.get('proxy_password')
|
||||
if proxy_username:
|
||||
UseBasicAuth(service, proxy_username, proxy_password, True)
|
||||
connection = http.client.HTTPConnection(p_server, p_port)
|
||||
if not full_uri.startswith("http://"):
|
||||
if full_uri.startswith("/"):
|
||||
full_uri = "http://%s%s" % (service.server, full_uri)
|
||||
else:
|
||||
full_uri = "http://%s/%s" % (service.server, full_uri)
|
||||
else:
|
||||
connection = http.client.HTTPConnection(server, port)
|
||||
full_uri = partial_uri
|
||||
|
||||
return (connection, full_uri)
|
||||
|
||||
|
||||
def UseBasicAuth(service, username, password, for_proxy=False):
|
||||
"""Sets an Authenticaiton: Basic HTTP header containing plaintext.
|
||||
|
||||
Deprecated, use AtomService.use_basic_auth insread.
|
||||
|
||||
The username and password are base64 encoded and added to an HTTP header
|
||||
which will be included in each request. Note that your username and
|
||||
password are sent in plaintext. The auth header is added to the
|
||||
additional_headers dictionary in the service object.
|
||||
|
||||
Args:
|
||||
service: atom.AtomService or a subclass which has an
|
||||
additional_headers dict as a member.
|
||||
username: str
|
||||
password: str
|
||||
"""
|
||||
deprecation('calling deprecated function UseBasicAuth')
|
||||
base_64_string = base64.encodestring('%s:%s' % (username, password))
|
||||
base_64_string = base_64_string.strip()
|
||||
if for_proxy:
|
||||
header_name = 'Proxy-Authorization'
|
||||
else:
|
||||
header_name = 'Authorization'
|
||||
service.additional_headers[header_name] = 'Basic %s' % (base_64_string,)
|
||||
|
||||
|
||||
def ProcessUrl(service, url, for_proxy=False):
|
||||
"""Processes a passed URL. If the URL does not begin with https?, then
|
||||
the default value for server is used
|
||||
|
||||
This method is deprecated, use atom.url.parse_url instead.
|
||||
"""
|
||||
if not isinstance(url, atom.url.Url):
|
||||
url = atom.url.parse_url(url)
|
||||
|
||||
server = url.host
|
||||
ssl = False
|
||||
port = 80
|
||||
|
||||
if not server:
|
||||
if hasattr(service, 'server'):
|
||||
server = service.server
|
||||
else:
|
||||
server = service
|
||||
if not url.protocol and hasattr(service, 'ssl'):
|
||||
ssl = service.ssl
|
||||
if hasattr(service, 'port'):
|
||||
port = service.port
|
||||
else:
|
||||
if url.protocol == 'https':
|
||||
ssl = True
|
||||
elif url.protocol == 'http':
|
||||
ssl = False
|
||||
if url.port:
|
||||
port = int(url.port)
|
||||
elif port == 80 and ssl:
|
||||
port = 443
|
||||
|
||||
return (server, port, ssl, url.get_request_uri())
|
||||
|
||||
|
||||
def DictionaryToParamList(url_parameters, escape_params=True):
|
||||
"""Convert a dictionary of URL arguments into a URL parameter string.
|
||||
|
||||
This function is deprcated, use atom.url.Url instead.
|
||||
|
||||
Args:
|
||||
url_parameters: The dictionaty of key-value pairs which will be converted
|
||||
into URL parameters. For example,
|
||||
{'dry-run': 'true', 'foo': 'bar'}
|
||||
will become ['dry-run=true', 'foo=bar'].
|
||||
|
||||
Returns:
|
||||
A list which contains a string for each key-value pair. The strings are
|
||||
ready to be incorporated into a URL by using '&'.join([] + parameter_list)
|
||||
"""
|
||||
# Choose which function to use when modifying the query and parameters.
|
||||
# Use quote_plus when escape_params is true.
|
||||
transform_op = [str, urllib.parse.quote_plus][bool(escape_params)]
|
||||
# Create a list of tuples containing the escaped version of the
|
||||
# parameter-value pairs.
|
||||
parameter_tuples = [(transform_op(param), transform_op(value))
|
||||
for param, value in list((url_parameters or {}).items())]
|
||||
# Turn parameter-value tuples into a list of strings in the form
|
||||
# 'PARAMETER=VALUE'.
|
||||
return ['='.join(x) for x in parameter_tuples]
|
||||
|
||||
|
||||
def BuildUri(uri, url_params=None, escape_params=True):
|
||||
"""Converts a uri string and a collection of parameters into a URI.
|
||||
|
||||
This function is deprcated, use atom.url.Url instead.
|
||||
|
||||
Args:
|
||||
uri: string
|
||||
url_params: dict (optional)
|
||||
escape_params: boolean (optional)
|
||||
uri: string The start of the desired URI. This string can alrady contain
|
||||
URL parameters. Examples: '/base/feeds/snippets',
|
||||
'/base/feeds/snippets?bq=digital+camera'
|
||||
url_parameters: dict (optional) Additional URL parameters to be included
|
||||
in the query. These are translated into query arguments
|
||||
in the form '&dict_key=value&...'.
|
||||
Example: {'max-results': '250'} becomes &max-results=250
|
||||
escape_params: boolean (optional) If false, the calling code has already
|
||||
ensured that the query will form a valid URL (all
|
||||
reserved characters have been escaped). If true, this
|
||||
method will escape the query and any URL parameters
|
||||
provided.
|
||||
|
||||
Returns:
|
||||
string The URI consisting of the escaped URL parameters appended to the
|
||||
initial uri string.
|
||||
"""
|
||||
# Prepare URL parameters for inclusion into the GET request.
|
||||
parameter_list = DictionaryToParamList(url_params, escape_params)
|
||||
|
||||
# Append the URL parameters to the URL.
|
||||
if parameter_list:
|
||||
if uri.find('?') != -1:
|
||||
# If there are already URL parameters in the uri string, add the
|
||||
# parameters after a new & character.
|
||||
full_uri = '&'.join([uri] + parameter_list)
|
||||
else:
|
||||
# The uri string did not have any URL parameters (no ? character)
|
||||
# so put a ? between the uri and URL parameters.
|
||||
full_uri = '%s%s' % (uri, '?%s' % ('&'.join([] + parameter_list)))
|
||||
else:
|
||||
full_uri = uri
|
||||
|
||||
return full_uri
|
||||
|
||||
|
||||
def HttpRequest(service, operation, data, uri, extra_headers=None,
|
||||
url_params=None, escape_params=True, content_type='application/atom+xml'):
|
||||
"""Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE.
|
||||
|
||||
This method is deprecated, use atom.http.HttpClient.request instead.
|
||||
|
||||
Usage example, perform and HTTP GET on http://www.google.com/:
|
||||
import atom.service
|
||||
client = atom.service.AtomService()
|
||||
http_response = client.Get('http://www.google.com/')
|
||||
or you could set the client.server to 'www.google.com' and use the
|
||||
following:
|
||||
client.server = 'www.google.com'
|
||||
http_response = client.Get('/')
|
||||
|
||||
Args:
|
||||
service: atom.AtomService object which contains some of the parameters
|
||||
needed to make the request. The following members are used to
|
||||
construct the HTTP call: server (str), additional_headers (dict),
|
||||
port (int), and ssl (bool).
|
||||
operation: str The HTTP operation to be performed. This is usually one of
|
||||
'GET', 'POST', 'PUT', or 'DELETE'
|
||||
data: ElementTree, filestream, list of parts, or other object which can be
|
||||
converted to a string.
|
||||
Should be set to None when performing a GET or PUT.
|
||||
If data is a file-like object which can be read, this method will read
|
||||
a chunk of 100K bytes at a time and send them.
|
||||
If the data is a list of parts to be sent, each part will be evaluated
|
||||
and sent.
|
||||
uri: The beginning of the URL to which the request should be sent.
|
||||
Examples: '/', '/base/feeds/snippets',
|
||||
'/m8/feeds/contacts/default/base'
|
||||
extra_headers: dict of strings. HTTP headers which should be sent
|
||||
in the request. These headers are in addition to those stored in
|
||||
service.additional_headers.
|
||||
url_params: dict of strings. Key value pairs to be added to the URL as
|
||||
URL parameters. For example {'foo':'bar', 'test':'param'} will
|
||||
become ?foo=bar&test=param.
|
||||
escape_params: bool default True. If true, the keys and values in
|
||||
url_params will be URL escaped when the form is constructed
|
||||
(Special characters converted to %XX form.)
|
||||
content_type: str The MIME type for the data being sent. Defaults to
|
||||
'application/atom+xml', this is only used if data is set.
|
||||
"""
|
||||
deprecation('call to deprecated function HttpRequest')
|
||||
full_uri = BuildUri(uri, url_params, escape_params)
|
||||
(connection, full_uri) = PrepareConnection(service, full_uri)
|
||||
|
||||
if extra_headers is None:
|
||||
extra_headers = {}
|
||||
|
||||
# Turn on debug mode if the debug member is set.
|
||||
if service.debug:
|
||||
connection.debuglevel = 1
|
||||
|
||||
connection.putrequest(operation, full_uri)
|
||||
|
||||
# If the list of headers does not include a Content-Length, attempt to
|
||||
# calculate it based on the data object.
|
||||
if (data and 'Content-Length' not in service.additional_headers and
|
||||
'Content-Length' not in extra_headers):
|
||||
content_length = CalculateDataLength(data)
|
||||
if content_length:
|
||||
extra_headers['Content-Length'] = str(content_length)
|
||||
|
||||
if content_type:
|
||||
extra_headers['Content-Type'] = content_type
|
||||
|
||||
# Send the HTTP headers.
|
||||
if isinstance(service.additional_headers, dict):
|
||||
for header in service.additional_headers:
|
||||
connection.putheader(header, service.additional_headers[header])
|
||||
if isinstance(extra_headers, dict):
|
||||
for header in extra_headers:
|
||||
connection.putheader(header, extra_headers[header])
|
||||
connection.endheaders()
|
||||
|
||||
# If there is data, send it in the request.
|
||||
if data:
|
||||
if isinstance(data, list):
|
||||
for data_part in data:
|
||||
__SendDataPart(data_part, connection)
|
||||
else:
|
||||
__SendDataPart(data, connection)
|
||||
|
||||
# Return the HTTP Response from the server.
|
||||
return connection.getresponse()
|
||||
|
||||
|
||||
def __SendDataPart(data, connection):
|
||||
"""This method is deprecated, use atom.http._send_data_part"""
|
||||
deprecated('call to deprecated function __SendDataPart')
|
||||
if isinstance(data, str):
|
||||
# TODO add handling for unicode.
|
||||
connection.send(data)
|
||||
return
|
||||
elif ElementTree.iselement(data):
|
||||
connection.send(ElementTree.tostring(data))
|
||||
return
|
||||
# Check to see if data is a file-like object that has a read method.
|
||||
elif hasattr(data, 'read'):
|
||||
# Read the file and send it a chunk at a time.
|
||||
while 1:
|
||||
binarydata = data.read(100000)
|
||||
if binarydata == '': break
|
||||
connection.send(binarydata)
|
||||
return
|
||||
else:
|
||||
# The data object was not a file.
|
||||
# Try to convert to a string and send the data.
|
||||
connection.send(str(data))
|
||||
return
|
||||
|
||||
|
||||
def CalculateDataLength(data):
|
||||
"""Attempts to determine the length of the data to send.
|
||||
|
||||
This method will respond with a length only if the data is a string or
|
||||
and ElementTree element.
|
||||
|
||||
Args:
|
||||
data: object If this is not a string or ElementTree element this funtion
|
||||
will return None.
|
||||
"""
|
||||
if isinstance(data, str):
|
||||
return len(data)
|
||||
elif isinstance(data, list):
|
||||
return None
|
||||
elif ElementTree.iselement(data):
|
||||
return len(ElementTree.tostring(data))
|
||||
elif hasattr(data, 'read'):
|
||||
# If this is a file-like object, don't try to guess the length.
|
||||
return None
|
||||
else:
|
||||
return len(str(data).encode('utf-8'))
|
||||
|
||||
|
||||
def deprecation(message):
|
||||
warnings.warn(message, DeprecationWarning, stacklevel=2)
|
||||
@@ -1,105 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2008 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License 2.0;
|
||||
|
||||
|
||||
"""This module provides a TokenStore class which is designed to manage
|
||||
auth tokens required for different services.
|
||||
|
||||
Each token is valid for a set of scopes which is the start of a URL. An HTTP
|
||||
client will use a token store to find a valid Authorization header to send
|
||||
in requests to the specified URL. If the HTTP client determines that a token
|
||||
has expired or been revoked, it can remove the token from the store so that
|
||||
it will not be used in future requests.
|
||||
"""
|
||||
|
||||
# __author__ = 'api.jscudder (Jeff Scudder)'
|
||||
|
||||
import atom.http_interface
|
||||
import atom.url
|
||||
|
||||
SCOPE_ALL = 'http'
|
||||
|
||||
|
||||
class TokenStore(object):
|
||||
"""Manages Authorization tokens which will be sent in HTTP headers."""
|
||||
|
||||
def __init__(self, scoped_tokens=None):
|
||||
self._tokens = scoped_tokens or {}
|
||||
|
||||
def add_token(self, token):
|
||||
"""Adds a new token to the store (replaces tokens with the same scope).
|
||||
|
||||
Args:
|
||||
token: A subclass of http_interface.GenericToken. The token object is
|
||||
responsible for adding the Authorization header to the HTTP request.
|
||||
The scopes defined in the token are used to determine if the token
|
||||
is valid for a requested scope when find_token is called.
|
||||
|
||||
Returns:
|
||||
True if the token was added, False if the token was not added becase
|
||||
no scopes were provided.
|
||||
"""
|
||||
if not hasattr(token, 'scopes') or not token.scopes:
|
||||
return False
|
||||
|
||||
for scope in token.scopes:
|
||||
self._tokens[str(scope)] = token
|
||||
return True
|
||||
|
||||
def find_token(self, url):
|
||||
"""Selects an Authorization header token which can be used for the URL.
|
||||
|
||||
Args:
|
||||
url: str or atom.url.Url or a list containing the same.
|
||||
The URL which is going to be requested. All
|
||||
tokens are examined to see if any scopes begin match the beginning
|
||||
of the URL. The first match found is returned.
|
||||
|
||||
Returns:
|
||||
The token object which should execute the HTTP request. If there was
|
||||
no token for the url (the url did not begin with any of the token
|
||||
scopes available), then the atom.http_interface.GenericToken will be
|
||||
returned because the GenericToken calls through to the http client
|
||||
without adding an Authorization header.
|
||||
"""
|
||||
if url is None:
|
||||
return None
|
||||
if isinstance(url, str):
|
||||
url = atom.url.parse_url(url)
|
||||
if url in self._tokens:
|
||||
token = self._tokens[url]
|
||||
if token.valid_for_scope(url):
|
||||
return token
|
||||
else:
|
||||
del self._tokens[url]
|
||||
for scope, token in self._tokens.items():
|
||||
if token.valid_for_scope(url):
|
||||
return token
|
||||
return atom.http_interface.GenericToken()
|
||||
|
||||
def remove_token(self, token):
|
||||
"""Removes the token from the token_store.
|
||||
|
||||
This method is used when a token is determined to be invalid. If the
|
||||
token was found by find_token, but resulted in a 401 or 403 error stating
|
||||
that the token was invlid, then the token should be removed to prevent
|
||||
future use.
|
||||
|
||||
Returns:
|
||||
True if a token was found and then removed from the token
|
||||
store. False if the token was not in the TokenStore.
|
||||
"""
|
||||
token_found = False
|
||||
scopes_to_delete = []
|
||||
for scope, stored_token in self._tokens.items():
|
||||
if stored_token == token:
|
||||
scopes_to_delete.append(scope)
|
||||
token_found = True
|
||||
for scope in scopes_to_delete:
|
||||
del self._tokens[scope]
|
||||
return token_found
|
||||
|
||||
def remove_all_tokens(self):
|
||||
self._tokens = {}
|
||||
@@ -1,130 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2008 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License 2.0;
|
||||
|
||||
|
||||
|
||||
# __author__ = 'api.jscudder (Jeff Scudder)'
|
||||
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
DEFAULT_PROTOCOL = 'http'
|
||||
DEFAULT_PORT = 80
|
||||
|
||||
|
||||
def parse_url(url_string):
|
||||
"""Creates a Url object which corresponds to the URL string.
|
||||
|
||||
This method can accept partial URLs, but it will leave missing
|
||||
members of the Url unset.
|
||||
"""
|
||||
parts = urllib.parse.urlparse(url_string)
|
||||
url = Url()
|
||||
if parts[0]:
|
||||
url.protocol = parts[0]
|
||||
if parts[1]:
|
||||
host_parts = parts[1].split(':')
|
||||
if host_parts[0]:
|
||||
url.host = host_parts[0]
|
||||
if len(host_parts) > 1:
|
||||
url.port = host_parts[1]
|
||||
if parts[2]:
|
||||
url.path = parts[2]
|
||||
if parts[4]:
|
||||
param_pairs = parts[4].split('&')
|
||||
for pair in param_pairs:
|
||||
pair_parts = pair.split('=')
|
||||
if len(pair_parts) > 1:
|
||||
url.params[urllib.parse.unquote_plus(pair_parts[0])] = (
|
||||
urllib.parse.unquote_plus(pair_parts[1]))
|
||||
elif len(pair_parts) == 1:
|
||||
url.params[urllib.parse.unquote_plus(pair_parts[0])] = None
|
||||
return url
|
||||
|
||||
|
||||
class Url(object):
|
||||
"""Represents a URL and implements comparison logic.
|
||||
|
||||
URL strings which are not identical can still be equivalent, so this object
|
||||
provides a better interface for comparing and manipulating URLs than
|
||||
strings. URL parameters are represented as a dictionary of strings, and
|
||||
defaults are used for the protocol (http) and port (80) if not provided.
|
||||
"""
|
||||
|
||||
def __init__(self, protocol=None, host=None, port=None, path=None,
|
||||
params=None):
|
||||
self.protocol = protocol
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.path = path
|
||||
self.params = params or {}
|
||||
|
||||
def to_string(self):
|
||||
url_parts = ['', '', '', '', '', '']
|
||||
if self.protocol:
|
||||
url_parts[0] = self.protocol
|
||||
if self.host:
|
||||
if self.port:
|
||||
url_parts[1] = ':'.join((self.host, str(self.port)))
|
||||
else:
|
||||
url_parts[1] = self.host
|
||||
if self.path:
|
||||
url_parts[2] = self.path
|
||||
if self.params:
|
||||
url_parts[4] = self.get_param_string()
|
||||
return urllib.parse.urlunparse(url_parts)
|
||||
|
||||
def get_param_string(self):
|
||||
param_pairs = []
|
||||
for key, value in self.params.items():
|
||||
param_pairs.append('='.join((urllib.parse.quote_plus(key),
|
||||
urllib.parse.quote_plus(str(value)))))
|
||||
return '&'.join(param_pairs)
|
||||
|
||||
def get_request_uri(self):
|
||||
"""Returns the path with the parameters escaped and appended."""
|
||||
param_string = self.get_param_string()
|
||||
if param_string:
|
||||
return '?'.join([self.path, param_string])
|
||||
else:
|
||||
return self.path
|
||||
|
||||
def __cmp__(self, other):
|
||||
if not isinstance(other, Url):
|
||||
return cmp(self.to_string(), str(other))
|
||||
difference = 0
|
||||
# Compare the protocol
|
||||
if self.protocol and other.protocol:
|
||||
difference = cmp(self.protocol, other.protocol)
|
||||
elif self.protocol and not other.protocol:
|
||||
difference = cmp(self.protocol, DEFAULT_PROTOCOL)
|
||||
elif not self.protocol and other.protocol:
|
||||
difference = cmp(DEFAULT_PROTOCOL, other.protocol)
|
||||
if difference != 0:
|
||||
return difference
|
||||
# Compare the host
|
||||
difference = cmp(self.host, other.host)
|
||||
if difference != 0:
|
||||
return difference
|
||||
# Compare the port
|
||||
if self.port and other.port:
|
||||
difference = cmp(self.port, other.port)
|
||||
elif self.port and not other.port:
|
||||
difference = cmp(self.port, DEFAULT_PORT)
|
||||
elif not self.port and other.port:
|
||||
difference = cmp(DEFAULT_PORT, other.port)
|
||||
if difference != 0:
|
||||
return difference
|
||||
# Compare the path
|
||||
difference = cmp(self.path, other.path)
|
||||
if difference != 0:
|
||||
return difference
|
||||
# Compare the parameters
|
||||
return cmp(self.params, other.params)
|
||||
|
||||
def __str__(self):
|
||||
return self.to_string()
|
||||
@@ -1,694 +0,0 @@
|
||||
#
|
||||
# This is a custom certificate authority bundle for GAM
|
||||
# It's composed of Let's Encrypt Root CAs and Google's
|
||||
# certificate bundle. This should be the minimal list of
|
||||
# CAs required to talk to Google and Github.
|
||||
|
||||
# Operating CA: Let's Encrypt
|
||||
# Issuer: C = US, O = Internet Security Research Group, CN = ISRG Root X1
|
||||
# Subject: C = US, O = Internet Security Research Group, CN = ISRG Root X1
|
||||
# Label: "ISRG Root X1"
|
||||
# Serial: 172886928669790476064670243504169061120
|
||||
# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e
|
||||
# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8
|
||||
# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
|
||||
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
|
||||
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
|
||||
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
|
||||
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
|
||||
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
|
||||
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
|
||||
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
|
||||
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
|
||||
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
|
||||
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
|
||||
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
|
||||
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
|
||||
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
|
||||
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
|
||||
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
|
||||
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
|
||||
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
|
||||
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
|
||||
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
|
||||
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
|
||||
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
|
||||
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
|
||||
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
|
||||
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
|
||||
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
|
||||
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
|
||||
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
|
||||
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: Let's Encrypt
|
||||
# Issuer: C = US, O = Internet Security Research Group, CN = ISRG Root X2
|
||||
# Subject: C = US, O = Internet Security Research Group, CN = ISRG Root X2
|
||||
# Label: "ISRG Root X2"
|
||||
# Serial: 87493402998870891108772069816698636114
|
||||
# MD5 Fingerprint: d3:9e:c4:1e:23:3c:a6:df:cf:a3:7e:6d:e0:14:e6:e5
|
||||
# SHA1 Fingerprint: bd:b1:b9:3c:d5:97:8d:45:c6:26:14:55:f8:db:95:c7:5a:d1:53:af
|
||||
# SHA256 Fingerprint: 69:72:9b:8e:15:a8:6e:fc:17:7a:57:af:b7:17:1d:fc:64:ad:d2:8c:2f:ca:8c:f1:50:7e:34:45:3c:cb:14:70
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
|
||||
CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
|
||||
R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
|
||||
MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
|
||||
ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
|
||||
EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
|
||||
+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
|
||||
ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
|
||||
AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
|
||||
zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
|
||||
tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
|
||||
/q4AaOeMSQ+2b1tbFfLn
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: DigiCert
|
||||
# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com
|
||||
# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com
|
||||
# Label: "DigiCert Assured ID Root G2"
|
||||
# Serial: 15385348160840213938643033620894905419
|
||||
# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d
|
||||
# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f
|
||||
# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl
|
||||
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
||||
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
|
||||
b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG
|
||||
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
|
||||
cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi
|
||||
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA
|
||||
n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc
|
||||
biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp
|
||||
EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA
|
||||
bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu
|
||||
YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB
|
||||
AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW
|
||||
BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI
|
||||
QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I
|
||||
0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni
|
||||
lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9
|
||||
B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv
|
||||
ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo
|
||||
IhNzbM8m9Yop5w==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: DigiCert
|
||||
# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com
|
||||
# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com
|
||||
# Label: "DigiCert Assured ID Root G3"
|
||||
# Serial: 15459312981008553731928384953135426796
|
||||
# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb
|
||||
# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89
|
||||
# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw
|
||||
CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
|
||||
ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg
|
||||
RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV
|
||||
UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
|
||||
Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq
|
||||
hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf
|
||||
Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q
|
||||
RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
|
||||
BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD
|
||||
AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY
|
||||
JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv
|
||||
6pZjamVFkpUBtA==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
|
||||
# Operating CA: DigiCert
|
||||
# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com
|
||||
# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com
|
||||
# Label: "DigiCert Global Root G2"
|
||||
# Serial: 4293743540046975378534879503202253541
|
||||
# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44
|
||||
# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4
|
||||
# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
|
||||
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
||||
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
|
||||
MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
|
||||
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
|
||||
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
|
||||
2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
|
||||
1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
|
||||
q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
|
||||
tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
|
||||
vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
|
||||
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
|
||||
5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
|
||||
1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
|
||||
NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
|
||||
Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
|
||||
8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
|
||||
pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
|
||||
MrY=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: DigiCert
|
||||
# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com
|
||||
# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com
|
||||
# Label: "DigiCert Global Root G3"
|
||||
# Serial: 7089244469030293291760083333884364146
|
||||
# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca
|
||||
# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e
|
||||
# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw
|
||||
CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
|
||||
ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe
|
||||
Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw
|
||||
EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x
|
||||
IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF
|
||||
K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG
|
||||
fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO
|
||||
Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd
|
||||
BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx
|
||||
AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/
|
||||
oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8
|
||||
sycX
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
|
||||
# Operating CA: DigiCert
|
||||
# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com
|
||||
# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com
|
||||
# Label: "DigiCert Trusted Root G4"
|
||||
# Serial: 7451500558977370777930084869016614236
|
||||
# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49
|
||||
# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4
|
||||
# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi
|
||||
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
||||
d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg
|
||||
RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV
|
||||
UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
|
||||
Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG
|
||||
SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y
|
||||
ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If
|
||||
xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV
|
||||
ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO
|
||||
DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ
|
||||
jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/
|
||||
CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi
|
||||
EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM
|
||||
fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY
|
||||
uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK
|
||||
chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t
|
||||
9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
|
||||
hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD
|
||||
ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2
|
||||
SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd
|
||||
+SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc
|
||||
fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa
|
||||
sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N
|
||||
cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N
|
||||
0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie
|
||||
4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI
|
||||
r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1
|
||||
/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm
|
||||
gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: GlobalSign
|
||||
# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3
|
||||
# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3
|
||||
# Label: "GlobalSign Root CA - R3"
|
||||
# Serial: 4835703278459759426209954
|
||||
# MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28
|
||||
# SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad
|
||||
# SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
|
||||
A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
|
||||
Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
|
||||
MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG
|
||||
A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
|
||||
hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8
|
||||
RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT
|
||||
gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm
|
||||
KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd
|
||||
QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ
|
||||
XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw
|
||||
DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o
|
||||
LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU
|
||||
RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp
|
||||
jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK
|
||||
6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX
|
||||
mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs
|
||||
Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH
|
||||
WD9f
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: GlobalSign
|
||||
# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5
|
||||
# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5
|
||||
# Label: "GlobalSign ECC Root CA - R5"
|
||||
# Serial: 32785792099990507226680698011560947931244
|
||||
# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08
|
||||
# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa
|
||||
# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk
|
||||
MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH
|
||||
bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
|
||||
DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD
|
||||
QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
|
||||
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc
|
||||
8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke
|
||||
hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
|
||||
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI
|
||||
KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg
|
||||
515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO
|
||||
xwy8p2Fp8fc74SrL+SvzZpA3
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: GlobalSign
|
||||
# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6
|
||||
# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6
|
||||
# Label: "GlobalSign Root CA - R6"
|
||||
# Serial: 1417766617973444989252670301619537
|
||||
# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae
|
||||
# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1
|
||||
# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg
|
||||
MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh
|
||||
bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx
|
||||
MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET
|
||||
MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ
|
||||
KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI
|
||||
xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k
|
||||
ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD
|
||||
aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw
|
||||
LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw
|
||||
1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX
|
||||
k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2
|
||||
SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h
|
||||
bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n
|
||||
WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY
|
||||
rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce
|
||||
MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD
|
||||
AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu
|
||||
bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN
|
||||
nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt
|
||||
Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61
|
||||
55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj
|
||||
vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf
|
||||
cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz
|
||||
oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp
|
||||
nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs
|
||||
pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v
|
||||
JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R
|
||||
8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4
|
||||
5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Note: "GlobalSign Root CA - R7" not added on purpose. It is P-521.
|
||||
|
||||
# Operating CA: GoDaddy
|
||||
# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc.
|
||||
# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc.
|
||||
# Label: "Go Daddy Root Certificate Authority - G2"
|
||||
# Serial: 0
|
||||
# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01
|
||||
# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b
|
||||
# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx
|
||||
EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT
|
||||
EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp
|
||||
ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz
|
||||
NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH
|
||||
EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE
|
||||
AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw
|
||||
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD
|
||||
E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH
|
||||
/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy
|
||||
DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh
|
||||
GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR
|
||||
tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA
|
||||
AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
|
||||
FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX
|
||||
WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu
|
||||
9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr
|
||||
gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo
|
||||
2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
|
||||
LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI
|
||||
4uJEvlz36hz1
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: GoDaddy
|
||||
# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc.
|
||||
# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc.
|
||||
# Label: "Starfield Root Certificate Authority - G2"
|
||||
# Serial: 0
|
||||
# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96
|
||||
# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e
|
||||
# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx
|
||||
EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
|
||||
HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs
|
||||
ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw
|
||||
MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6
|
||||
b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj
|
||||
aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp
|
||||
Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
|
||||
ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg
|
||||
nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1
|
||||
HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N
|
||||
Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN
|
||||
dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0
|
||||
HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
|
||||
BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G
|
||||
CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU
|
||||
sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3
|
||||
4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg
|
||||
8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K
|
||||
pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1
|
||||
mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: Sectigo
|
||||
# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited
|
||||
# Subject: CN=COMODO Certification Authority O=COMODO CA Limited
|
||||
# Label: "COMODO Certification Authority"
|
||||
# Serial: 43390818032842818540635488309124489234
|
||||
# MD5 Fingerprint: 20:E7:4F:82:C2:7E:94:80:34:82:8A:13:A9:17:1D:97
|
||||
# SHA1 Fingerprint EE:86:93:87:FF:FD:83:49:AB:5A:D1:43:22:58:87:89:A4:57:B0:12
|
||||
# SHA256 Fingerprint: 1A:0D:20:44:5D:E5:BA:18:62:D1:9E:F8:80:85:8C:BC:E5:01:02:B3:6E:8F:0A:04:0C:3C:69:E7:45:22:FE:6E
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID0DCCArigAwIBAgIQIKTEf93f4cdTYwcTiHdgEjANBgkqhkiG9w0BAQUFADCB
|
||||
gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
|
||||
A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
|
||||
BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xMTAxMDEwMDAw
|
||||
MDBaFw0zMDEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl
|
||||
YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P
|
||||
RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0
|
||||
aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3
|
||||
UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI
|
||||
2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8
|
||||
Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp
|
||||
+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+
|
||||
DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O
|
||||
nKVIrLsm9wIDAQABo0IwQDAdBgNVHQ4EFgQUC1jli8ZMFTekQKkwqSG+RzZaVv8w
|
||||
DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
|
||||
ggEBAC/JxBwHO89hAgCx2SFRdXIDMLDEFh9sAIsQrK/xR9SuEDwMGvjUk2ysEDd8
|
||||
t6aDZK3N3w6HM503sMZ7OHKx8xoOo/lVem0DZgMXlUrxsXrfViEGQo+x06iF3u6X
|
||||
HWLrp+cxEmbDD6ZLLkGC9/3JG6gbr+48zuOcrigHoSybJMIPIyaDMouGDx8rEkYl
|
||||
Fo92kANr3ryqImhrjKGsKxE5pttwwn1y6TPn/CbxdFqR5p2ErPioBhlG5qfpqjQi
|
||||
pKGfeq23sqSaM4hxAjwu1nqyH6LKwN0vEJT9s4yEIHlG1QXUEOTS22RPuFvuG8Ug
|
||||
R1uUq27UlTMdphVx8fiUylQ5PsE=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: Sectigo
|
||||
# Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited
|
||||
# Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited
|
||||
# Label: "COMODO ECC Certification Authority"
|
||||
# Serial: 41578283867086692638256921589707938090
|
||||
# MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23
|
||||
# SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11
|
||||
# SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL
|
||||
MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
|
||||
BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT
|
||||
IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw
|
||||
MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy
|
||||
ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N
|
||||
T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv
|
||||
biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR
|
||||
FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J
|
||||
cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW
|
||||
BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
|
||||
BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm
|
||||
fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv
|
||||
GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: Sectigo
|
||||
# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited
|
||||
# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited
|
||||
# Label: "COMODO RSA Certification Authority"
|
||||
# Serial: 101909084537582093308941363524873193117
|
||||
# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18
|
||||
# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4
|
||||
# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB
|
||||
hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
|
||||
A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
|
||||
BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5
|
||||
MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
|
||||
EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
|
||||
Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh
|
||||
dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR
|
||||
6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X
|
||||
pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC
|
||||
9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV
|
||||
/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf
|
||||
Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z
|
||||
+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w
|
||||
qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah
|
||||
SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC
|
||||
u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf
|
||||
Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq
|
||||
crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E
|
||||
FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB
|
||||
/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl
|
||||
wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM
|
||||
4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV
|
||||
2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna
|
||||
FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ
|
||||
CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK
|
||||
boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke
|
||||
jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL
|
||||
S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb
|
||||
QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl
|
||||
0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB
|
||||
NVOFBkpdn627G190
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: Sectigo
|
||||
# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network
|
||||
# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network
|
||||
# Label: "USERTrust ECC Certification Authority"
|
||||
# Serial: 123013823720199481456569720443997572134
|
||||
# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1
|
||||
# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0
|
||||
# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL
|
||||
MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl
|
||||
eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT
|
||||
JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx
|
||||
MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
|
||||
Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg
|
||||
VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm
|
||||
aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo
|
||||
I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng
|
||||
o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G
|
||||
A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD
|
||||
VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB
|
||||
zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW
|
||||
RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: Sectigo
|
||||
# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network
|
||||
# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network
|
||||
# Label: "USERTrust RSA Certification Authority"
|
||||
# Serial: 2645093764781058787591871645665788717
|
||||
# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5
|
||||
# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e
|
||||
# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
|
||||
iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
|
||||
cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
|
||||
BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw
|
||||
MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV
|
||||
BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
|
||||
aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy
|
||||
dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
|
||||
AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B
|
||||
3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY
|
||||
tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/
|
||||
Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2
|
||||
VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT
|
||||
79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6
|
||||
c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT
|
||||
Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l
|
||||
c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee
|
||||
UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE
|
||||
Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
|
||||
BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G
|
||||
A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF
|
||||
Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO
|
||||
VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3
|
||||
ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs
|
||||
8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR
|
||||
iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze
|
||||
Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ
|
||||
XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/
|
||||
qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB
|
||||
VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
|
||||
L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
|
||||
jjxDah2nGN59PRbxYvnKkKj9
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: Google Trust Services LLC
|
||||
# Subject: C = US, O = Google Trust Services LLC, CN = GTS Root R1
|
||||
# Issuer: C = US, O = Google Trust Services LLC, CN = GTS Root R1
|
||||
# Label: "GTS Root R1"
|
||||
# Serial: 0203E5936F31B01349886BA217
|
||||
# MD5 Fingerprint: 05:FE:D0:BF:71:A8:A3:76:63:DA:01:E0:D8:52:DC:40
|
||||
# SHA1 Fingerprint: E5:8C:1C:C4:91:3B:38:63:4B:E9:10:6E:E3:AD:8E:6B:9D:D9:81:4A
|
||||
# SHA256 Fingerprint: D9:47:43:2A:BD:E7:B7:FA:90:FC:2E:6B:59:10:1B:12:80:E0:E1:C7:E4:E4:0F:A3:C6:88:7F:FF:57:A7:F4:CF
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw
|
||||
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
|
||||
MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
|
||||
MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
|
||||
Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA
|
||||
A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo
|
||||
27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w
|
||||
Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw
|
||||
TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl
|
||||
qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH
|
||||
szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8
|
||||
Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk
|
||||
MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92
|
||||
wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p
|
||||
aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN
|
||||
VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID
|
||||
AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
|
||||
FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb
|
||||
C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe
|
||||
QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy
|
||||
h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4
|
||||
7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J
|
||||
ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef
|
||||
MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/
|
||||
Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT
|
||||
6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ
|
||||
0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm
|
||||
2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb
|
||||
bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: Google Trust Services LLC
|
||||
# Subject: C = US, O = Google Trust Services LLC, CN = GTS Root R2
|
||||
# Issuer: C = US, O = Google Trust Services LLC, CN = GTS Root R2
|
||||
# Label: "GTS Root R2"
|
||||
# Serial: 0203E5AEC58D04251AAB1125AA
|
||||
# MD5 Fingerprint=1E:39:C0:53:E6:1E:29:82:0B:CA:52:55:36:5D:57:DC
|
||||
# SHA1 Fingerprint=9A:44:49:76:32:DB:DE:FA:D0:BC:FB:5A:7B:17:BD:9E:56:09:24:94
|
||||
# SHA256 Fingerprint=8D:25:CD:97:22:9D:BF:70:35:6B:DA:4E:B3:CC:73:40:31:E2:4C:F0:0F:AF:CF:D3:2D:C7:6E:B5:84:1C:7E:A8
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw
|
||||
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
|
||||
MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
|
||||
MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
|
||||
Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA
|
||||
A4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt
|
||||
nfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY
|
||||
6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu
|
||||
MC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k
|
||||
RXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg
|
||||
f9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV
|
||||
+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo
|
||||
dDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW
|
||||
Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa
|
||||
G73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq
|
||||
gc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID
|
||||
AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
|
||||
FgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H
|
||||
vqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8
|
||||
0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC
|
||||
B19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u
|
||||
NmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg
|
||||
yALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev
|
||||
HyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6
|
||||
xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR
|
||||
TOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg
|
||||
JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV
|
||||
7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl
|
||||
6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: Google Trust Services LLC
|
||||
# Subject: C = US, O = Google Trust Services LLC, CN = GTS Root R3
|
||||
# Issuer: C = US, O = Google Trust Services LLC, CN = GTS Root R3
|
||||
# Label: "GTS Root R3"
|
||||
# Serial: 0203E5B882EB20F825276D3D66
|
||||
# MD5 Fingerprint: 3E:E7:9D:58:02:94:46:51:94:E5:E0:22:4A:8B:E7:73
|
||||
# SHA1 Fingerprint: ED:E5:71:80:2B:C8:92:B9:5B:83:3C:D2:32:68:3F:09:CD:A0:1E:46
|
||||
# SHA256 Fingerprint: 34:D8:A7:3E:E2:08:D9:BC:DB:0D:95:65:20:93:4B:4E:40:E6:94:82:59:6E:8B:6F:73:C8:42:6B:01:0A:6F:48
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD
|
||||
VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
|
||||
A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
|
||||
WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz
|
||||
IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
|
||||
AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G
|
||||
jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2
|
||||
4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
|
||||
BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7
|
||||
VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm
|
||||
ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: Google Trust Services LLC
|
||||
# Subject: C = US, O = Google Trust Services LLC, CN = GTS Root R4
|
||||
# Issuer: C = US, O = Google Trust Services LLC, CN = GTS Root R4
|
||||
# Label: "GTS Root R4"
|
||||
# Serial: 0203E5C068EF631A9C72905052
|
||||
# MD5 Fingerprint=43:96:83:77:19:4D:76:B3:9D:65:52:E4:1D:22:A5:E8
|
||||
# SHA1 Fingerprint=77:D3:03:67:B5:E0:0C:15:F6:0C:38:61:DF:7C:E1:3B:92:46:4D:47
|
||||
# SHA256 Fingerprint=34:9D:FA:40:58:C5:E2:63:12:3B:39:8A:E7:95:57:3C:4E:13:13:C8:3F:E6:8F:93:55:6C:D5:E8:03:1B:3C:7D
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD
|
||||
VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
|
||||
A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
|
||||
WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz
|
||||
IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
|
||||
AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi
|
||||
QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR
|
||||
HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
|
||||
BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D
|
||||
9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8
|
||||
p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: Google Trust Services LLC
|
||||
# Subject: OU = GlobalSign ECC Root CA - R4, O = GlobalSign, CN = GlobalSign
|
||||
# Issuer: OU = GlobalSign ECC Root CA - R4, O = GlobalSign, CN = GlobalSign
|
||||
# Label: "GlobalSign R4"
|
||||
# Serial: 0203E57EF53F93FDA50921B2A6
|
||||
# MD5 Fingerprint: 26:29:F8:6D:E1:88:BF:A2:65:7F:AA:C4:CD:0F:7F:FC
|
||||
# SHA1 Fingerprint: 6B:A0:B0:98:E1:71:EF:5A:AD:FE:48:15:80:77:10:F4:BD:6F:0B:28
|
||||
# SHA256 Fingerprint: B0:85:D7:0B:96:4F:19:1A:73:E4:AF:0D:54:AE:7A:0E:07:AA:FD:AF:9B:71:DD:08:62:13:8A:B7:32:5A:24:A2
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD
|
||||
VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh
|
||||
bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw
|
||||
MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g
|
||||
UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT
|
||||
BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx
|
||||
uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV
|
||||
HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/
|
||||
+wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147
|
||||
bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,593 +0,0 @@
|
||||
{
|
||||
"auth": {
|
||||
"oauth2": {
|
||||
"scopes": {
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers": {
|
||||
"description": "View and manage your Chrome browsers registered with Cloud Management"
|
||||
},
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers.readonly": {
|
||||
"description": "View your Chrome browsers registered with Cloud Management"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"basePath": "",
|
||||
"baseUrl": "https://admin.googleapis.com/admin/directory/v1.1beta1/customer/",
|
||||
"batchPath": "batch",
|
||||
"canonicalName": "cbcm",
|
||||
"discoveryVersion": "v1",
|
||||
"documentationLink": "https://support.google.com/chrome/a/answer/9681204",
|
||||
"fullyEncodeReservedExpansion": true,
|
||||
"icons": {
|
||||
"x16": "http://www.google.com/images/icons/product/search-16.gif",
|
||||
"x32": "http://www.google.com/images/icons/product/search-32.gif"
|
||||
},
|
||||
"id": "cbcm:v1.1beta1",
|
||||
"kind": "discovery#restDescription",
|
||||
"mtlsRootUrl": "https://admin.mtls.googleapis.com/",
|
||||
"name": "cbcm",
|
||||
"ownerDomain": "google.com",
|
||||
"ownerName": "Jay Lee",
|
||||
"packagePath": "cbcm",
|
||||
"parameters": {
|
||||
"$.xgafv": {
|
||||
"description": "V1 error format.",
|
||||
"enum": [
|
||||
"1",
|
||||
"2"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"v1 error format",
|
||||
"v2 error format"
|
||||
],
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"access_token": {
|
||||
"description": "OAuth access token.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"alt": {
|
||||
"default": "json",
|
||||
"description": "Data format for response.",
|
||||
"enum": [
|
||||
"json",
|
||||
"media",
|
||||
"proto"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Responses with Content-Type of application/json",
|
||||
"Media download with context-dependent Content-Type",
|
||||
"Responses with Content-Type of application/x-protobuf"
|
||||
],
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"callback": {
|
||||
"description": "JSONP",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"fields": {
|
||||
"description": "Selector specifying which fields to include in a partial response.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"oauth_token": {
|
||||
"description": "OAuth 2.0 token for the current user.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"prettyPrint": {
|
||||
"default": "true",
|
||||
"description": "Returns response with indentations and line breaks.",
|
||||
"location": "query",
|
||||
"type": "boolean"
|
||||
},
|
||||
"quotaUser": {
|
||||
"description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"uploadType": {
|
||||
"description": "Legacy upload protocol for media (e.g. \"media\", \"multipart\").",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"upload_protocol": {
|
||||
"description": "Upload protocol for media (e.g. \"raw\", \"multipart\").",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"protocol": "rest",
|
||||
"resources": {
|
||||
"chromebrowsers": {
|
||||
"methods": {
|
||||
"delete": {
|
||||
"description": "Deletes a browser.",
|
||||
"flatPath": "{customer}/devices/chromebrowsers/{deviceId}",
|
||||
"httpMethod": "DELETE",
|
||||
"id": "cbcm.chromebrowsers.delete",
|
||||
"parameterOrder": [
|
||||
"customer",
|
||||
"deviceId"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"deviceId": {
|
||||
"description": "Immutable ID of the browser.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/devices/chromebrowsers/{deviceId}",
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers"
|
||||
]
|
||||
},
|
||||
"get": {
|
||||
"description": "Retrieves a browser.",
|
||||
"flatPath": "{customer}/devices/chromebrowsers/{deviceId}",
|
||||
"httpMethod": "GET",
|
||||
"id": "cbcm.chromebrowsers.get",
|
||||
"parameterOrder": [
|
||||
"customer",
|
||||
"deviceId"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"deviceId": {
|
||||
"description": "Immutable ID of the browser.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"projection": {
|
||||
"description": "Restrict information returned to a set of selected fields. FULL or BASIC.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/devices/chromebrowsers/{deviceId}",
|
||||
"response": {
|
||||
"$ref": "ChromeBrowser"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers",
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers.readonly"
|
||||
]
|
||||
},
|
||||
"list": {
|
||||
"description": "Retrieves a paginated list of all the browsers in a domain.",
|
||||
"flatPath": "{customer}/devices/chromebrowsers",
|
||||
"httpMethod": "GET",
|
||||
"id": "cbcm.chromebrowsers.list",
|
||||
"parameterOrder": [
|
||||
"customer"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"maxResults": {
|
||||
"description": "Maximum number of results to return.",
|
||||
"format": "int32",
|
||||
"location": "query",
|
||||
"maximum": "100",
|
||||
"minimum": "1",
|
||||
"type": "integer"
|
||||
},
|
||||
"orderBy": {
|
||||
"description": "property to use for sorting results.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"orgUnitPath": {
|
||||
"description": "The full path of the organizational unit or its unique ID.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"pageToken": {
|
||||
"description": "Token to specify the next page in the list.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"projection": {
|
||||
"description": "Restrict information returned to a set of selected fields. FULL or BASIC.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"query": {
|
||||
"description": "Search string using the list page query language.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"sortOrder": {
|
||||
"description": "Whether to return results in ascending or descending order. Must be used with the orderBy parameter.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/devices/chromebrowsers",
|
||||
"response": {
|
||||
"$ref": "ChromeBrowsers"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers",
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers.readonly"
|
||||
]
|
||||
},
|
||||
"moveChromeBrowsersToOu": {
|
||||
"description": "Move Chrome Browsers Device between Organization Units",
|
||||
"flatPath": "{customer}/devices/chromebrowsers/moveChromeBrowsersToOu",
|
||||
"httpMethod": "POST",
|
||||
"id": "cbcm.chromebrowsers.moveChromeBrowsersToOu",
|
||||
"parameterOrder": [
|
||||
"customer"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/devices/chromebrowsers/moveChromeBrowsersToOu",
|
||||
"request": {
|
||||
"$ref": "MoveChromeBrowsersRequest"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers"
|
||||
]
|
||||
},
|
||||
"update": {
|
||||
"description": "Updates a browser.",
|
||||
"flatPath": "{customer}/devices/chromebrowsers/{deviceId}",
|
||||
"httpMethod": "PUT",
|
||||
"id": "cbcm.chromebrowsers.update",
|
||||
"parameterOrder": [
|
||||
"customer",
|
||||
"deviceId"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"deviceId": {
|
||||
"description": "Immutable ID of the browser.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"projection": {
|
||||
"description": "BASIC or FULL",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/devices/chromebrowsers/{deviceId}",
|
||||
"request": {
|
||||
"$ref": "ChromeBrowser"
|
||||
},
|
||||
"response": {
|
||||
"$ref": "ChromeBrowser"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"enrollmentTokens": {
|
||||
"methods": {
|
||||
"list": {
|
||||
"description": "Retrieves a paginated list of all the browser entollment tokens in a domain.",
|
||||
"flatPath": "{customer}/chrome/enrollmentTokens",
|
||||
"httpMethod": "GET",
|
||||
"id": "cbcm.enrollmentTokens.list",
|
||||
"parameterOrder": [
|
||||
"customer"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"pageSize": {
|
||||
"description": "Maximum number of results to return.",
|
||||
"format": "int32",
|
||||
"location": "query",
|
||||
"maximum": "100",
|
||||
"minimum": "1",
|
||||
"type": "integer"
|
||||
},
|
||||
"orgUnitPath": {
|
||||
"description": "The full path of the organizational unit or its unique ID.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"pageToken": {
|
||||
"description": "Token to specify the next page in the list.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"query": {
|
||||
"description": "Search string using the list page query language.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/chrome/enrollmentTokens",
|
||||
"response": {
|
||||
"$ref": "EnrollmentTokens"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers",
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers.readonly"
|
||||
]
|
||||
},
|
||||
"create": {
|
||||
"description": "Creates a browser enrollment token in a domain.",
|
||||
"flatPath": "{customer}/chrome/enrollmentTokens",
|
||||
"httpMethod": "POST",
|
||||
"id": "cbcm.enrollmentTokens.create",
|
||||
"parameterOrder": [
|
||||
"customer"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/chrome/enrollmentTokens",
|
||||
"request": {
|
||||
"$ref": "CreateEnrollmentTokenRequest"
|
||||
},
|
||||
"response": {
|
||||
"$ref": "EnrollmentToken"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers"
|
||||
]
|
||||
},
|
||||
"revoke": {
|
||||
"description": "Revokes a browser enrollment token in a domain.",
|
||||
"flatPath": "{customer}/chrome/enrollmentTokens/{tokenPermanentId}:revoke",
|
||||
"httpMethod": "POST",
|
||||
"id": "cbcm.enrollmentTokens.revoke",
|
||||
"parameterOrder": [
|
||||
"customer",
|
||||
"tokenPermanentId"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"tokenPermanentId": {
|
||||
"description": "Unique identifier for an enrollment token.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/chrome/enrollmentTokens/{tokenPermanentId}:revoke",
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"revision": "20201203",
|
||||
"rootUrl": "https://admin.googleapis.com/admin/directory/v1.1beta1/customer/",
|
||||
"schemas": {
|
||||
"ChromeBrowser": {
|
||||
"id": "ChromeBrowser",
|
||||
"properties": {
|
||||
"annotatedAssetId": {
|
||||
"description": "Asset identifier as annotated by the administrator or specified during enrollment.",
|
||||
"type": "string"
|
||||
},
|
||||
"annotatedLocation": {
|
||||
"description": "Address or location of the device as annotated by the administrator.",
|
||||
"type": "string"
|
||||
},
|
||||
"annotatedNotes": {
|
||||
"description": "Notes about this device as annotated by the administrator",
|
||||
"type": "string"
|
||||
},
|
||||
"annotatedUser": {
|
||||
"description": "User of the device as annotated by the administrator.",
|
||||
"type": "string"
|
||||
},
|
||||
"deviceId": {
|
||||
"annotations": {
|
||||
"required": [
|
||||
"cbcm.chromebrowsers.update"
|
||||
]
|
||||
},
|
||||
"description": "The unique ID of the device.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ChromeBrowsers": {
|
||||
"id": "ChromeBrowsers",
|
||||
"properties": {
|
||||
"browsers": {
|
||||
"description": "List of Chrome browser objects.",
|
||||
"items": {
|
||||
"$ref": "ChromeBrowser"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"etag": {
|
||||
"description": "ETag of the resource.",
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"default": "admin#directory#chromeosdevices",
|
||||
"description": "Kind of resource this is.",
|
||||
"type": "string"
|
||||
},
|
||||
"nextPageToken": {
|
||||
"description": "Token used to access the next page of this result. To access the next page, use this token's value in the `pageToken` query string of this request.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"EnrollmentToken": {
|
||||
"id": "EnrollmentToken",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"default": "admin#directory#chromeEnrollmentToken",
|
||||
"description": "Kind of resource this is.",
|
||||
"type": "string"
|
||||
},
|
||||
"tokenId": {
|
||||
"description": "Enrollment Token ID.",
|
||||
"type": "string"
|
||||
},
|
||||
"tokenPermanentId": {
|
||||
"description": "Enrollment Token Permanent ID.",
|
||||
"type": "string"
|
||||
},
|
||||
"customerId": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"type": "string"
|
||||
},
|
||||
"orgUnitPath": {
|
||||
"description": "The full path of the organizational unit or its unique ID.",
|
||||
"type": "string"
|
||||
},
|
||||
"creatorId": {
|
||||
"description": "Creator ID.",
|
||||
"type": "string"
|
||||
},
|
||||
"createTime": {
|
||||
"description": "Creation Time.",
|
||||
"type": "string"
|
||||
},
|
||||
"revokerId": {
|
||||
"description": "Revoker ID.",
|
||||
"type": "string"
|
||||
},
|
||||
"revokeTime": {
|
||||
"description": "Revoke Time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"EnrollmentTokens": {
|
||||
"id": "EnrollmentTokens",
|
||||
"properties": {
|
||||
"chrome_enrollment_tokens": {
|
||||
"description": "List of Chrome browser enrollment token objects.",
|
||||
"items": {
|
||||
"$ref": "EnrollmentToken"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"kind": {
|
||||
"default": "admin#directory#chromeEnrollmentTokens",
|
||||
"description": "Kind of resource this is.",
|
||||
"type": "string"
|
||||
},
|
||||
"nextPageToken": {
|
||||
"description": "Token used to access the next page of this result. To access the next page, use this token's value in the `pageToken` query string of this request.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"CreateEnrollmentTokenRequest": {
|
||||
"id": "CreateEnrollmentTokenRequest",
|
||||
"properties": {
|
||||
"org_unit_path": {
|
||||
"description": "The full path of the organizational unit or its unique ID.",
|
||||
"type": "string"
|
||||
},
|
||||
"expire_time": {
|
||||
"description": "Expiration Time.",
|
||||
"type": "string"
|
||||
},
|
||||
"token_type": {
|
||||
"id": "token_type",
|
||||
"annotations": {
|
||||
"required": [
|
||||
"cbcm.enrollmentTokens.create"
|
||||
]
|
||||
},
|
||||
"description": "CHROME_BROWSER.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"MoveChromeBrowsersRequest": {
|
||||
"id": "MoveChromeBrowsersRequest",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"org_unit_path": {
|
||||
"annotations": {
|
||||
"required": [
|
||||
"cbcm.chromebrowsers.moveChromeBrowsersToOu"
|
||||
]
|
||||
},
|
||||
"description": "Destination organization unit to move devices to. Full path of the organizational unit or its ID prefixed with id:",
|
||||
"type": "string"
|
||||
},
|
||||
"resource_ids": {
|
||||
"annotations": {
|
||||
"required": [
|
||||
"cbcm.chromebrowsers.moveChromeBrowsersToOu"
|
||||
]
|
||||
},
|
||||
"description": "List of unique device IDs of Chrome Browser Devices to move. A maximum of 600 browsers may be moved per request.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"servicePath": "",
|
||||
"title": "Admin SDK API",
|
||||
"version": "cbcm_v1.1beta1"
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
{
|
||||
"auth": {
|
||||
"oauth2": {
|
||||
"scopes": {
|
||||
"https://www.googleapis.com/auth/admin.contact.delegation": {
|
||||
"description": "View and manage your Contact Delegation"
|
||||
},
|
||||
"https://www.googleapis.com/auth/admin.contact.delegation.readonly": {
|
||||
"description": "View your Contact Delegation"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"basePath": "",
|
||||
"baseUrl": "https://admin.googleapis.com/admin/contacts/v1/",
|
||||
"batchPath": "batch",
|
||||
"canonicalName": "contactdelegation",
|
||||
"description": "The Contact Delegation API allows Admins to delegate access of one user's, called the delegator, contacts to another user, called the delegate.",
|
||||
"discoveryVersion": "v1",
|
||||
"documentationLink": "https://developers.google.com/admin-sdk/contact-delegation",
|
||||
"fullyEncodeReservedExpansion": true,
|
||||
"icons": {
|
||||
"x16": "http://www.google.com/images/icons/product/search-16.gif",
|
||||
"x32": "http://www.google.com/images/icons/product/search-32.gif"
|
||||
},
|
||||
"id": "contactdelegation:v1",
|
||||
"kind": "discovery#restDescription",
|
||||
"name": "contactdelegation",
|
||||
"ownerDomain": "google.com",
|
||||
"ownerName": "Google",
|
||||
"packagePath": "admin",
|
||||
"parameters": {
|
||||
"$.xgafv": {
|
||||
"description": "V1 error format.",
|
||||
"enum": [
|
||||
"1",
|
||||
"2"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"v1 error format",
|
||||
"v2 error format"
|
||||
],
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"access_token": {
|
||||
"description": "OAuth access token.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"alt": {
|
||||
"default": "json",
|
||||
"description": "Data format for response.",
|
||||
"enum": [
|
||||
"json",
|
||||
"media",
|
||||
"proto"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Responses with Content-Type of application/json",
|
||||
"Media download with context-dependent Content-Type",
|
||||
"Responses with Content-Type of application/x-protobuf"
|
||||
],
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"callback": {
|
||||
"description": "JSONP",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"fields": {
|
||||
"description": "Selector specifying which fields to include in a partial response.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"oauth_token": {
|
||||
"description": "OAuth 2.0 token for the current user.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"prettyPrint": {
|
||||
"default": "true",
|
||||
"description": "Returns response with indentations and line breaks.",
|
||||
"location": "query",
|
||||
"type": "boolean"
|
||||
},
|
||||
"quotaUser": {
|
||||
"description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"uploadType": {
|
||||
"description": "Legacy upload protocol for media (e.g. \"media\", \"multipart\").",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"upload_protocol": {
|
||||
"description": "Upload protocol for media (e.g. \"raw\", \"multipart\").",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"protocol": "rest",
|
||||
"resources": {
|
||||
"delegates": {
|
||||
"methods": {
|
||||
"create": {
|
||||
"description": "Creates a contact delegations",
|
||||
"flatPath": "users/{user}/delegates",
|
||||
"httpMethod": "POST",
|
||||
"id": "contactdelegations.delegates.create",
|
||||
"parameterOrder": [
|
||||
"user"
|
||||
],
|
||||
"parameters": {
|
||||
"user": {
|
||||
"description": "Email address of the delegator.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "users/{user}/delegates/{delegate}",
|
||||
"request": {
|
||||
"$ref": "Delegate"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.contact.delegation"
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"description": "Deletes a contact delegation.",
|
||||
"flatPath": "users/{user}/delegates/{delegate}",
|
||||
"httpMethod": "DELETE",
|
||||
"id": "contactdelegations.delegates.delete",
|
||||
"parameterOrder": [
|
||||
"user",
|
||||
"delegate"
|
||||
],
|
||||
"parameters": {
|
||||
"delegate": {
|
||||
"description": "Email address of the delegate",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"user": {
|
||||
"description": "Email address of the delegator.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "users/{user}/delegates/{delegate}",
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.contact.delegation"
|
||||
]
|
||||
},
|
||||
"list": {
|
||||
"description": "Lists contact delegates for a user",
|
||||
"flatPath": "users/{user}/delegates",
|
||||
"httpMethod": "GET",
|
||||
"id": "contactdelegations.delegates.list",
|
||||
"parameterOrder": [
|
||||
"user"
|
||||
],
|
||||
"parameters": {
|
||||
"pageSize": {
|
||||
"description": "Determines how many delegates are returned in each response. ",
|
||||
"format": "int32",
|
||||
"location": "query",
|
||||
"minimum": "1",
|
||||
"type": "integer"
|
||||
},
|
||||
"pageToken": {
|
||||
"description": "Token to specify the next page in the list.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"user": {
|
||||
"description": "Email address of the delegator.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "users/{user}/delegates",
|
||||
"response": {
|
||||
"$ref": "Delegates"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.contact.delegation",
|
||||
"https://www.googleapis.com/auth/admin.contact.delegation.readonly"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"rootUrl": "https://admin.googleapis.com/admin/contacts/v1/",
|
||||
"schemas": {
|
||||
"Delegate": {
|
||||
"description": "JSON template for a delegate.",
|
||||
"id": "Delegate",
|
||||
"properties": {
|
||||
"email": {
|
||||
"description": "Email of the delegate.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Delegates": {
|
||||
"id": "Delegates",
|
||||
"properties": {
|
||||
"delegates": {
|
||||
"description": "List of delegates.",
|
||||
"items": {
|
||||
"$ref": "Delegate"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"etag": {
|
||||
"description": "ETag of the resource.",
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"default": "",
|
||||
"description": "Kind of resource this is.",
|
||||
"type": "string"
|
||||
},
|
||||
"nextPageToken": {
|
||||
"description": "Token used to access the next page of this result. To access the next page, use this token's value in the `pageToken` query string of this request.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"servicePath": "",
|
||||
"title": "Contact Delegation API",
|
||||
"version": "v1",
|
||||
"version_module": true
|
||||
}
|
||||
@@ -1,486 +0,0 @@
|
||||
{
|
||||
"basePath": "",
|
||||
"discoveryVersion": "v1",
|
||||
"documentationLink": "https://support.google.com/datastudio",
|
||||
"canonicalName": "Data Studio",
|
||||
"id": "datastudio:v1",
|
||||
"ownerName": "Google",
|
||||
"description": "Allows programmatic viewing and editing of Data Studio assets.",
|
||||
"rootUrl": "https://datastudio.googleapis.com/",
|
||||
"ownerDomain": "google.com",
|
||||
"mtlsRootUrl": "https://datastudio.mtls.googleapis.com/",
|
||||
"batchPath": "batch",
|
||||
"version_module": true,
|
||||
"version": "v1",
|
||||
"schemas": {
|
||||
"Asset": {
|
||||
"id": "Asset",
|
||||
"properties": {
|
||||
"title": {
|
||||
"description": "The title of the asset.",
|
||||
"type": "string"
|
||||
},
|
||||
"createTime": {
|
||||
"format": "google-datetime",
|
||||
"description": "Date the asset was created.",
|
||||
"type": "string"
|
||||
},
|
||||
"lastViewByMeTime": {
|
||||
"type": "string",
|
||||
"description": "Date the asset was last viewed by me.",
|
||||
"format": "google-datetime"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"description": "The owner of the asset."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the asset."
|
||||
},
|
||||
"trashed": {
|
||||
"type": "boolean",
|
||||
"description": "Value indicating if the asset is in the trash."
|
||||
},
|
||||
"updateTime": {
|
||||
"format": "google-datetime",
|
||||
"description": "Date the asset was last modified.",
|
||||
"type": "string"
|
||||
},
|
||||
"updateByMeTime": {
|
||||
"description": "Date the asset was last modified by me.",
|
||||
"type": "string",
|
||||
"format": "google-datetime"
|
||||
},
|
||||
"assetType": {
|
||||
"enumDescriptions": [
|
||||
"Asset type not specified.",
|
||||
"A report asset.",
|
||||
"A data Source asset."
|
||||
],
|
||||
"enum": [
|
||||
"ASSET_TYPE_UNSPECIFIED",
|
||||
"REPORT",
|
||||
"DATA_SOURCE"
|
||||
],
|
||||
"description": "The type of the asset.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "A Data Studio asset.",
|
||||
"type": "object"
|
||||
},
|
||||
"SearchAssetsResponse": {
|
||||
"id": "SearchAssetsResponse",
|
||||
"properties": {
|
||||
"assets": {
|
||||
"items": {
|
||||
"$ref": "Asset"
|
||||
},
|
||||
"type": "array",
|
||||
"description": "The list of assets."
|
||||
},
|
||||
"nextPageToken": {
|
||||
"type": "string",
|
||||
"description": "A token to retrieve next page of results. Pass this value in the SearchAssetsRequest.page_token field in the subsequent call to `SearchAssets` method to retrieve the next page of results."
|
||||
}
|
||||
},
|
||||
"description": "Response message for DataStudioService.SearchAssets",
|
||||
"type": "object"
|
||||
},
|
||||
"UpdatePermissionsRequest": {
|
||||
"description": "Request message for DataStudioService.UpdatePermissions",
|
||||
"properties": {
|
||||
"updateMask": {
|
||||
"description": "The list of fields to be updated. Currently not supported.",
|
||||
"type": "string",
|
||||
"format": "google-fieldmask"
|
||||
},
|
||||
"permissions": {
|
||||
"description": "The permissions object to update.",
|
||||
"$ref": "Permissions"
|
||||
}
|
||||
},
|
||||
"id": "UpdatePermissionsRequest",
|
||||
"type": "object"
|
||||
},
|
||||
"AddMembersRequest": {
|
||||
"properties": {
|
||||
"members": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Required. The members to add to the role. The format of a member is one of - user:alice@example.com - group:admins@example.com - domain:google.com - serviceAccount:my-app@appspot.gserviceaccount.com"
|
||||
},
|
||||
"role": {
|
||||
"type": "string",
|
||||
"enumDescriptions": [
|
||||
"Role not specified.",
|
||||
"A viewer.",
|
||||
"An editor.",
|
||||
"An owner.",
|
||||
"Link shared viewer.",
|
||||
"Link shared editor."
|
||||
],
|
||||
"enum": [
|
||||
"ROLE_UNSPECIFIED",
|
||||
"VIEWER",
|
||||
"EDITOR",
|
||||
"OWNER",
|
||||
"LINK_VIEWER",
|
||||
"LINK_EDITOR"
|
||||
],
|
||||
"description": "Required. The role to add members to."
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"id": "AddMembersRequest",
|
||||
"description": "Request message for DataStudioService.AddMembers"
|
||||
},
|
||||
"Permissions": {
|
||||
"type": "object",
|
||||
"id": "Permissions",
|
||||
"description": "A Data Studio asset's Permissions.",
|
||||
"properties": {
|
||||
"permissions": {
|
||||
"description": "A map from a Role to a list of members. Role is a string representation of the Role enum. One of: - OWNER - EDITOR - VIEWER",
|
||||
"additionalProperties": {
|
||||
"$ref": "Members"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"etag": {
|
||||
"format": "byte",
|
||||
"description": "etag to detect and fail concurrent modifications",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"RevokeAllPermissionsRequest": {
|
||||
"description": "Request message for DataStudioService.RevokeAllPermissions",
|
||||
"id": "RevokeAllPermissionsRequest",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"members": {
|
||||
"description": "Required. The members that are having their access revoked. The format of a member is one of - user:alice@example.com - group:admins@example.com - domain:google.com - serviceAccount:my-app@appspot.gserviceaccount.com",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Members": {
|
||||
"description": "A wrapper message for a list of members.",
|
||||
"properties": {
|
||||
"members": {
|
||||
"description": "Format of string is one of - user:alice@example.com - group:admins@example.com - domain:google.com - serviceAccount:my-app@appspot.gserviceaccount.com",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"id": "Members"
|
||||
}
|
||||
},
|
||||
"name": "datastudio",
|
||||
"protocol": "rest",
|
||||
"baseUrl": "https://datastudio.googleapis.com/",
|
||||
"title": "Data Studio API",
|
||||
"revision": "20210412",
|
||||
"fullyEncodeReservedExpansion": true,
|
||||
"icons": {
|
||||
"x32": "http://www.google.com/images/icons/product/search-32.gif",
|
||||
"x16": "http://www.google.com/images/icons/product/search-16.gif"
|
||||
},
|
||||
"parameters": {
|
||||
"quotaUser": {
|
||||
"location": "query",
|
||||
"description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters.",
|
||||
"type": "string"
|
||||
},
|
||||
"prettyPrint": {
|
||||
"location": "query",
|
||||
"type": "boolean",
|
||||
"description": "Returns response with indentations and line breaks.",
|
||||
"default": "true"
|
||||
},
|
||||
"callback": {
|
||||
"location": "query",
|
||||
"type": "string",
|
||||
"description": "JSONP"
|
||||
},
|
||||
"uploadType": {
|
||||
"description": "Legacy upload protocol for media (e.g. \"media\", \"multipart\").",
|
||||
"type": "string",
|
||||
"location": "query"
|
||||
},
|
||||
"upload_protocol": {
|
||||
"type": "string",
|
||||
"location": "query",
|
||||
"description": "Upload protocol for media (e.g. \"raw\", \"multipart\")."
|
||||
},
|
||||
"$.xgafv": {
|
||||
"enumDescriptions": [
|
||||
"v1 error format",
|
||||
"v2 error format"
|
||||
],
|
||||
"description": "V1 error format.",
|
||||
"location": "query",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"1",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
"oauth_token": {
|
||||
"type": "string",
|
||||
"location": "query",
|
||||
"description": "OAuth 2.0 token for the current user."
|
||||
},
|
||||
"alt": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"json",
|
||||
"media",
|
||||
"proto"
|
||||
],
|
||||
"location": "query",
|
||||
"description": "Data format for response.",
|
||||
"enumDescriptions": [
|
||||
"Responses with Content-Type of application/json",
|
||||
"Media download with context-dependent Content-Type",
|
||||
"Responses with Content-Type of application/x-protobuf"
|
||||
],
|
||||
"default": "json"
|
||||
},
|
||||
"fields": {
|
||||
"description": "Selector specifying which fields to include in a partial response.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"access_token": {
|
||||
"type": "string",
|
||||
"description": "OAuth access token.",
|
||||
"location": "query"
|
||||
},
|
||||
"key": {
|
||||
"type": "string",
|
||||
"location": "query",
|
||||
"description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token."
|
||||
}
|
||||
},
|
||||
"servicePath": "",
|
||||
"kind": "discovery#restDescription",
|
||||
"resources": {
|
||||
"assets": {
|
||||
"methods": {
|
||||
"getPermissions": {
|
||||
"parameters": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"location": "path",
|
||||
"description": "Required. The name of the asset.",
|
||||
"required": true
|
||||
},
|
||||
"role": {
|
||||
"enumDescriptions": [
|
||||
"Role not specified.",
|
||||
"A viewer.",
|
||||
"An editor.",
|
||||
"An owner.",
|
||||
"Link shared viewer.",
|
||||
"Link shared editor."
|
||||
],
|
||||
"type": "string",
|
||||
"location": "query",
|
||||
"description": "The role of the permssion.",
|
||||
"enum": [
|
||||
"ROLE_UNSPECIFIED",
|
||||
"VIEWER",
|
||||
"EDITOR",
|
||||
"OWNER",
|
||||
"LINK_VIEWER",
|
||||
"LINK_EDITOR"
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "datastudio.assets.getPermissions",
|
||||
"response": {
|
||||
"$ref": "Permissions"
|
||||
},
|
||||
"flatPath": "v1/assets/{name}/permissions",
|
||||
"path": "v1/assets/{name}/permissions",
|
||||
"description": "Gets the asset's permission for a given role.",
|
||||
"parameterOrder": [
|
||||
"name"
|
||||
],
|
||||
"httpMethod": "GET"
|
||||
},
|
||||
"updatePermissions": {
|
||||
"id": "datastudio.assets.updatePermissions",
|
||||
"parameterOrder": [
|
||||
"name"
|
||||
],
|
||||
"flatPath": "v1/assets/{name}/permissions",
|
||||
"description": "Updates a permission.",
|
||||
"request": {
|
||||
"$ref": "UpdatePermissionsRequest"
|
||||
},
|
||||
"path": "v1/assets/{name}/permissions",
|
||||
"parameters": {
|
||||
"name": {
|
||||
"description": "Required. The name of the asset.",
|
||||
"location": "path",
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"$ref": "Permissions"
|
||||
},
|
||||
"httpMethod": "PATCH"
|
||||
},
|
||||
"get": {
|
||||
"path": "v1/{+name}",
|
||||
"id": "datastudio.assets.get",
|
||||
"parameterOrder": [
|
||||
"name"
|
||||
],
|
||||
"description": "Gets the asset by name.",
|
||||
"parameters": {
|
||||
"name": {
|
||||
"description": "Required. The name of the asset. Format: assets/{asset}",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"pattern": "^assets/[^/]+$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"flatPath": "v1/assets/{assetsId}",
|
||||
"response": {
|
||||
"$ref": "Asset"
|
||||
},
|
||||
"httpMethod": "GET"
|
||||
},
|
||||
"search": {
|
||||
"response": {
|
||||
"$ref": "SearchAssetsResponse"
|
||||
},
|
||||
"path": "v1/assets:search",
|
||||
"parameters": {
|
||||
"pageToken": {
|
||||
"type": "string",
|
||||
"location": "query",
|
||||
"description": "A token identifying a page of results the server should return. Use the value of SearchAssetsResponse.next_page_token returned from the previous call to `SearchAssets` method."
|
||||
},
|
||||
"assetTypes": {
|
||||
"type": "string",
|
||||
"repeated": true,
|
||||
"description": "Exactly one AssetType must be specified.",
|
||||
"enumDescriptions": [
|
||||
"Asset type not specified.",
|
||||
"A report asset.",
|
||||
"A data Source asset."
|
||||
],
|
||||
"location": "query",
|
||||
"enum": [
|
||||
"ASSET_TYPE_UNSPECIFIED",
|
||||
"REPORT",
|
||||
"DATA_SOURCE"
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"description": "The title of assets to include. Not an exact match, works the same as search from the UI.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"location": "query",
|
||||
"description": "The email of the owner of the asset."
|
||||
},
|
||||
"pageSize": {
|
||||
"description": "Requested page size. If unspecified, server will pick an appropriate default.",
|
||||
"location": "query",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"orderBy": {
|
||||
"location": "query",
|
||||
"description": "How the results should be ordered. Valid options are: - title",
|
||||
"type": "string"
|
||||
},
|
||||
"includeTrashed": {
|
||||
"location": "query",
|
||||
"type": "boolean",
|
||||
"description": "Value indicating if assets in trash should be included."
|
||||
}
|
||||
},
|
||||
"flatPath": "v1/assets:search",
|
||||
"httpMethod": "GET",
|
||||
"description": "Searches assets.",
|
||||
"id": "datastudio.assets.search",
|
||||
"parameterOrder": []
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"permissions": {
|
||||
"methods": {
|
||||
"revokeAllPermissions": {
|
||||
"path": "v1/assets/{name}/permissions:revokeAllPermissions",
|
||||
"response": {
|
||||
"$ref": "Permissions"
|
||||
},
|
||||
"flatPath": "v1/assets/{name}/permissions:revokeAllPermissions",
|
||||
"id": "datastudio.assets.permissions.revokeAllPermissions",
|
||||
"description": "Revokes one or more members' access to an asset.",
|
||||
"parameters": {
|
||||
"name": {
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"location": "path",
|
||||
"description": "Required. The name of the asset."
|
||||
}
|
||||
},
|
||||
"parameterOrder": [
|
||||
"name"
|
||||
],
|
||||
"httpMethod": "POST",
|
||||
"request": {
|
||||
"$ref": "RevokeAllPermissionsRequest"
|
||||
}
|
||||
},
|
||||
"addMembers": {
|
||||
"path": "v1/assets/{name}/permissions:addMembers",
|
||||
"parameters": {
|
||||
"name": {
|
||||
"required": true,
|
||||
"location": "path",
|
||||
"type": "string",
|
||||
"description": "Required. The name of the asset."
|
||||
}
|
||||
},
|
||||
"httpMethod": "POST",
|
||||
"parameterOrder": [
|
||||
"name"
|
||||
],
|
||||
"response": {
|
||||
"$ref": "Permissions"
|
||||
},
|
||||
"id": "datastudio.assets.permissions.addMembers",
|
||||
"request": {
|
||||
"$ref": "AddMembersRequest"
|
||||
},
|
||||
"description": "Adds one or more members to a role.",
|
||||
"flatPath": "v1/assets/{name}/permissions:addMembers"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2023 Ross Scroggs All Rights Reserved.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
@@ -1,312 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2026 Ross Scroggs All Rights Reserved.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""GAM action processing
|
||||
|
||||
"""
|
||||
|
||||
class GamAction():
|
||||
|
||||
# Keys into NAMES; arbitrary values but must be unique
|
||||
ACCEPT = 'acpt'
|
||||
ADD = 'add '
|
||||
ADD_PREVIEW = 'addp'
|
||||
APPEND = 'apnd'
|
||||
APPROVE = 'aprv'
|
||||
ARCHIVE = 'arch'
|
||||
BACKUP = 'back'
|
||||
BLOCK = 'blok'
|
||||
CANCEL = 'canc'
|
||||
CANCEL_WIPE = 'canw'
|
||||
CHECK = 'chek'
|
||||
CLAIM = 'clai'
|
||||
CLAIM_OWNERSHIP = 'clow'
|
||||
CLEAR = 'clea'
|
||||
CLOSE = 'clos'
|
||||
COLLECT = 'coll'
|
||||
COMMENT = 'comm'
|
||||
COPY = 'copy'
|
||||
COPY_MERGE = 'copm'
|
||||
CREATE = 'crea'
|
||||
CREATE_PREVIEW = 'crep'
|
||||
CREATE_SHORTCUT = 'crsc'
|
||||
DEDUP = 'dedu'
|
||||
DELETE = 'dele'
|
||||
DELETE_EMPTY = 'delm'
|
||||
DELETE_PREVIEW = 'delp'
|
||||
DELETE_SHORTCUT = 'desc'
|
||||
DEPROVISION = 'depr'
|
||||
DISABLE = 'disa'
|
||||
DOWNLOAD = 'down'
|
||||
DRAFT = 'draf'
|
||||
EMPTY = 'empt'
|
||||
ENABLE = 'enbl'
|
||||
END = 'end '
|
||||
EXISTS = 'exis'
|
||||
EXPORT = 'expo'
|
||||
EXTRACT = 'extr'
|
||||
GET_COMMAND_RESULT = 'gtcr'
|
||||
FETCH = 'fetc'
|
||||
FORWARD = 'forw'
|
||||
HIDE = 'hide'
|
||||
IMPORT = 'impo'
|
||||
INFO = 'info'
|
||||
INITIALIZE = 'init'
|
||||
INSERT = 'insr'
|
||||
INVALIDATE = 'inva'
|
||||
ISSUE_COMMAND = 'isco'
|
||||
LIST = 'list'
|
||||
LOOKUP = 'look'
|
||||
MERGE = 'merg'
|
||||
MODIFY = 'modi'
|
||||
MOVE = 'move'
|
||||
MOVE_MERGE = 'movm'
|
||||
NOACTION = 'noac'
|
||||
NOACTION_PREVIEW = 'noap'
|
||||
OBLITERATE = 'obli'
|
||||
PERFORM = 'perf'
|
||||
PRE_PROVISIONED_DISABLE ='ppdi'
|
||||
PRE_PROVISIONED_REENABLE ='ppre'
|
||||
PRINT = 'prin'
|
||||
PROCESS = 'proc'
|
||||
PROCESS_PREVIEW = 'prop'
|
||||
PURGE = 'purg'
|
||||
RECREATE = 'recr'
|
||||
REENABLE = 'reen'
|
||||
REFRESH = 'refr'
|
||||
RELABEL = 'rela'
|
||||
REMOVE = 'remo'
|
||||
REMOVE_PREVIEW = 'remp'
|
||||
RENAME = 'rena'
|
||||
REOPEN = 'reop'
|
||||
REPLACE = 'repl'
|
||||
REPLACE_DOMAIN = 'repd'
|
||||
REPORT = 'repo'
|
||||
RESET_YUBIKEY_PIV = 'rpiv'
|
||||
RESPOND = 'resp'
|
||||
RESTORE = 'rest'
|
||||
RESUBMIT = 'res'
|
||||
RETAIN = 'reta'
|
||||
RETRIEVE_DATA = 'retd'
|
||||
REVOKE = 'revo'
|
||||
SAVE = 'save'
|
||||
SEND = 'send'
|
||||
SENDEMAIL = 'snem'
|
||||
SENDREPLY = 'sner'
|
||||
SET = 'set '
|
||||
SETUP = 'setu'
|
||||
SHARE = 'shar'
|
||||
SHOW = 'show'
|
||||
SIGNOUT = 'siou'
|
||||
SKIP = 'skip'
|
||||
SPAM = 'spam'
|
||||
SUBMIT = 'subm'
|
||||
SUSPEND = 'susp'
|
||||
SYNC = 'sync'
|
||||
TRANSFER = 'tran'
|
||||
TRANSFER_OWNERSHIP = 'trow'
|
||||
TRASH = 'tras'
|
||||
TURNOFF2SV = 'to2s'
|
||||
UNDELETE = 'unde'
|
||||
UNHIDE = 'unhi'
|
||||
UNSUSPEND = 'unsu'
|
||||
UNTRASH = 'untr'
|
||||
UPDATE = 'upda'
|
||||
UPDATE_MOVE = 'upmo'
|
||||
UPDATE_OWNER = 'updo'
|
||||
UPDATE_PREVIEW = 'updp'
|
||||
UPLOAD = 'uplo'
|
||||
UNZIP = 'unzi'
|
||||
USE = 'use '
|
||||
VERIFY = 'vrfy'
|
||||
VERIFYITEMEXISTS = 'vexi'
|
||||
WAITFORMAILBOX = 'wamb'
|
||||
WATCH = 'watc'
|
||||
WIPE = 'wipe'
|
||||
WIPE_PREVIEW = 'wipp'
|
||||
# Usage:
|
||||
# ACTION_NAMES[1] n Items - Delete 10 Users
|
||||
# Item xxx ACTION_NAMES[0] - User xxx Deleted
|
||||
# These values can be translated into other languages
|
||||
_NAMES = {
|
||||
ACCEPT: ['Accepted', 'Accept'],
|
||||
ADD: ['Added', 'Add'],
|
||||
ADD_PREVIEW: ['Added (Preview)', 'Add (Preview)'],
|
||||
APPEND: ['Appended', 'Append'],
|
||||
APPROVE: ['Approved', 'Approve'],
|
||||
ARCHIVE: ['Archived', 'Archive'],
|
||||
BACKUP: ['Backed up', 'Backup'],
|
||||
BLOCK: ['Blocked', 'Block'],
|
||||
CANCEL: ['Cancelled', 'Cancel'],
|
||||
CANCEL_WIPE: ['Wipe Cancelled', 'Cancel Wipe'],
|
||||
CHECK: ['Checked', 'Check'],
|
||||
CLAIM: ['Claimed', 'Claim'],
|
||||
CLAIM_OWNERSHIP: ['Ownership Claimed', 'Claim Ownership'],
|
||||
CLEAR: ['Cleared', 'Clear'],
|
||||
CLOSE: ['Closed', 'Close'],
|
||||
COLLECT: ['Collected', 'Collect'],
|
||||
COMMENT: ['Commented', 'Comment'],
|
||||
COPY: ['Copied', 'Copy'],
|
||||
COPY_MERGE: ['Copied(Merge)', 'Copy(Merge)'],
|
||||
CREATE: ['Created', 'Create'],
|
||||
CREATE_PREVIEW: ['Created (Preview)', 'Create (Preview)'],
|
||||
CREATE_SHORTCUT: ['Created Shortcut', 'Create Shortcut'],
|
||||
DEDUP: ['Duplicates Deleted', 'Delete Duplicates'],
|
||||
DELETE: ['Deleted', 'Delete'],
|
||||
DELETE_EMPTY: ['Deleted', 'Delete Empty'],
|
||||
DELETE_PREVIEW: ['Deleted (Preview)', 'Delete (Preview)'],
|
||||
DELETE_SHORTCUT: ['Deleted Shortcut', 'Delete Shortcut'],
|
||||
DEPROVISION: ['Deprovisioned', 'Deprovision'],
|
||||
DISABLE: ['Disabled', 'Disable'],
|
||||
DOWNLOAD: ['Downloaded', 'Download'],
|
||||
DRAFT: ['Drafted', 'Draft'],
|
||||
EMPTY: ['Emptied', 'Empty'],
|
||||
ENABLE: ['Enabled', 'Enable'],
|
||||
END: ['Ended', 'End'],
|
||||
EXISTS: ['Exists', 'Exists'],
|
||||
EXPORT: ['Exported', 'Export'],
|
||||
EXTRACT: ['Extracted', 'Extract'],
|
||||
FORWARD: ['Forwarded', 'Forward'],
|
||||
GET_COMMAND_RESULT: ['Got Command Result', 'Get Command Result'],
|
||||
HIDE: ['Hidden', 'Hide'],
|
||||
IMPORT: ['Imported', 'Import'],
|
||||
INFO: ['Shown', 'Show Info'],
|
||||
INITIALIZE: ['Initialized', 'Initialize'],
|
||||
INSERT: ['Inserted', 'Insert'],
|
||||
INVALIDATE: ['Invalidated', 'Invalidate'],
|
||||
ISSUE_COMMAND: ['Command Issued', 'Issue Command'],
|
||||
LIST: ['Listed', 'List'],
|
||||
LOOKUP: ['Lookedup', 'Lookup'],
|
||||
MERGE: ['Merged', 'Merge'],
|
||||
MODIFY: ['Modified', 'Modify'],
|
||||
MOVE: ['Moved', 'Move'],
|
||||
MOVE_MERGE: ['Moved(Merge)', 'Move(Merge)'],
|
||||
NOACTION: ['No Action', 'No Action'],
|
||||
NOACTION_PREVIEW: ['No Action (Preview)', 'No Action (Preview)'],
|
||||
OBLITERATE: ['Obliterated', 'Obliterate'],
|
||||
PERFORM: ['Action Performed', 'Perform Action'],
|
||||
PRE_PROVISIONED_DISABLE: ['PreProvisioned Disabled', 'PreProvisioned Disable'],
|
||||
PRE_PROVISIONED_REENABLE: ['PreProvisioned Reenabled', 'PreProvisioned Reenable'],
|
||||
PRINT: ['Printed', 'Print'],
|
||||
PROCESS: ['Processed', 'Process'],
|
||||
PROCESS_PREVIEW: ['Processed (Preview)', 'Process (Preview)'],
|
||||
PURGE: ['Purged', 'Purge'],
|
||||
RECREATE: ['Recreated', 'Recreate'],
|
||||
REENABLE: ['Reenabled', 'Reenable'],
|
||||
REFRESH: ['Refreshed', 'Refresh'],
|
||||
RELABEL: ['Relabeled', 'Relabel'],
|
||||
REMOVE: ['Removed', 'Remove'],
|
||||
REMOVE_PREVIEW: ['Removed (Preview)', 'Remove (Preview)'],
|
||||
RENAME: ['Renamed', 'Rename'],
|
||||
REOPEN: ['Reopened', 'Reopen'],
|
||||
REPLACE: ['Replaced', 'Replace'],
|
||||
REPLACE_DOMAIN: ['Domain Replaced', 'Replace Domain'],
|
||||
REPORT: ['Reported', 'Report'],
|
||||
RESET_YUBIKEY_PIV: ['Yubikey PIV Reset', 'Reset Yubikey PIV'],
|
||||
RESPOND: ['Responded', 'Respond'],
|
||||
RESTORE: ['Restored', 'Restore'],
|
||||
RESUBMIT: ['Resubmitted', 'Resubmit'],
|
||||
RETAIN: ['Retained', 'Retain'],
|
||||
RETRIEVE_DATA: ['Data Retrieved', 'Retrieve Data'],
|
||||
REVOKE: ['Revoked', 'Revoke'],
|
||||
SAVE: ['Saved', 'Save'],
|
||||
SEND: ['Sent', 'Send'],
|
||||
SENDEMAIL: ['Email Sent', 'Send Email'],
|
||||
SENDREPLY: ['Reply Sent', 'Send Reply'],
|
||||
SET: ['Set', 'Set'],
|
||||
SETUP: ['Set Up', 'Set Up'],
|
||||
SHARE: ['Shared', 'Share'],
|
||||
SHOW: ['Shown', 'Show'],
|
||||
SIGNOUT: ['Signed Out', 'Signout'],
|
||||
SKIP: ['Skipped', 'Skip'],
|
||||
SPAM: ['Marked as Spam', 'Mark as Spam'],
|
||||
SUBMIT: ['Submitted', 'Submit'],
|
||||
SUSPEND: ['Suspended', 'Suspend'],
|
||||
SYNC: ['Synced', 'Sync'],
|
||||
TRANSFER: ['Transferred', 'Transfer'],
|
||||
TRANSFER_OWNERSHIP: ['Ownership Transferred', 'Transfer Ownership'],
|
||||
TRASH: ['Trashed', 'Trash'],
|
||||
TURNOFF2SV: ['2-Step Verification Turned Off', 'Turn Off 2-Step Verification'],
|
||||
UNDELETE: ['Undeleted', 'Undelete'],
|
||||
UNHIDE: ['Unhidden', 'Unhide'],
|
||||
UNSUSPEND: ['Unsuspended', 'Unsuspend'],
|
||||
UNTRASH: ['Untrashed', 'Untrash'],
|
||||
UNZIP: ['Unzipped', 'Unzip'],
|
||||
UPDATE: ['Updated', 'Update'],
|
||||
UPDATE_MOVE: ['Updated/Moved', 'Update/Move'],
|
||||
UPDATE_OWNER: ['Updated to Owner', 'Update to Owner'],
|
||||
UPDATE_PREVIEW: ['Updated (Preview)', 'Update (Preview)'],
|
||||
UPLOAD: ['Uploaded', 'Upload'],
|
||||
USE: ['Used', 'Use'],
|
||||
VERIFY: ['Verified', 'Verify'],
|
||||
VERIFYITEMEXISTS: ['Verified Item Exists', 'Verify Item Exists'],
|
||||
WAITFORMAILBOX: ['Mailbox is Setup', 'Check Mailbox is Setup'],
|
||||
WATCH: ['Watched', 'Watch'],
|
||||
WIPE: ['Wiped', 'Wipe'],
|
||||
WIPE_PREVIEW: ['Wiped (Preview)', 'Wipe (Preview)'],
|
||||
}
|
||||
#
|
||||
MODIFIER_CONTENTS_WITH = 'contents with'
|
||||
MODIFIER_FOR = 'for'
|
||||
MODIFIER_FROM = 'from'
|
||||
MODIFIER_IN = 'in'
|
||||
MODIFIER_INTO = 'into'
|
||||
MODIFIER_PREVIOUSLY_IN = 'previously in'
|
||||
MODIFIER_TO = 'to'
|
||||
MODIFIER_WITH_COTEACHER_OWNER = 'with co-teacher as owner'
|
||||
MODIFIER_WITH_NEW_TEACHER_OWNER = 'with new teacher as owner'
|
||||
MODIFIER_WITH_CURRENT_OWNER = 'with current owner'
|
||||
MODIFIER_WITH = 'with'
|
||||
MODIFIER_WITH_CONTENT_FROM = 'with content from'
|
||||
PREFIX_NOT = 'Not'
|
||||
PREVIEW = 'Preview'
|
||||
SUCCESS = 'Success'
|
||||
SUFFIX_FAILED = 'Failed'
|
||||
|
||||
def __init__(self):
|
||||
self.action = None
|
||||
|
||||
def Set(self, action):
|
||||
self.action = action
|
||||
|
||||
def Get(self):
|
||||
return self.action
|
||||
|
||||
def ToPerform(self):
|
||||
return self._NAMES[self.action][1]
|
||||
|
||||
def Performed(self):
|
||||
return self._NAMES[self.action][0]
|
||||
|
||||
def Failed(self):
|
||||
return f'{self._NAMES[self.action][1]} {self.SUFFIX_FAILED}'
|
||||
|
||||
def NotPerformed(self):
|
||||
actionWords = self._NAMES[self.action][0].split(' ')
|
||||
if len(actionWords) != 2:
|
||||
return f'{self.PREFIX_NOT} {self._NAMES[self.action][0]}'
|
||||
return f'{actionWords[0]} {self.PREFIX_NOT} {actionWords[1]}'
|
||||
|
||||
def PerformedName(self, action):
|
||||
return self._NAMES[action][0]
|
||||
|
||||
def ToPerformName(self, action):
|
||||
return self._NAMES[action][1]
|
||||
|
||||
def csvFormat(self):
|
||||
return self.action == self.PRINT
|
||||
@@ -1,859 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2026 Ross Scroggs All Rights Reserved.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Google API resources
|
||||
|
||||
"""
|
||||
# APIs
|
||||
ACCESSCONTEXTMANAGER = 'accesscontextmanager'
|
||||
ALERTCENTER = 'alertcenter'
|
||||
ANALYTICS_ADMIN = 'analyticsadmin'
|
||||
CALENDAR = 'calendar'
|
||||
BUSINESSACCOUNTMANAGEMENT = 'mybusinessaccountmanagement'
|
||||
CBCM = 'cbcm'
|
||||
CHAT = 'chat'
|
||||
CHAT_CUSTOM_EMOJIS = 'chatcustomemojis'
|
||||
CHAT_EVENTS = 'chatevents'
|
||||
CHAT_MEMBERSHIPS = 'chatmemberships'
|
||||
CHAT_MEMBERSHIPS_ADMIN = 'chatmembershipsadmin'
|
||||
CHAT_MESSAGES = 'chatmessages'
|
||||
CHAT_SECTIONS = 'chatsections'
|
||||
CHAT_SPACES = 'chatspaces'
|
||||
CHAT_SPACES_ADMIN = 'chatspacesadmin'
|
||||
CHAT_SPACES_DELETE = 'chatspacesdelete'
|
||||
CHAT_SPACES_DELETE_ADMIN = 'chatspacesdeleteadmin'
|
||||
CHROMEMANAGEMENT = 'chromemanagement'
|
||||
CHROMEMANAGEMENT_APPDETAILS = 'chromemanagementappdetails'
|
||||
CHROMEMANAGEMENT_CHROMEPROFILES = 'chromemanagementchromeprofiles'
|
||||
CHROMEMANAGEMENT_TELEMETRY = 'chromemanagementtelemetry'
|
||||
CHROMEPOLICY = 'chromepolicy'
|
||||
CHROMEVERSIONHISTORY = 'versionhistory'
|
||||
CLASSROOM = 'classroom'
|
||||
CLOUDCHANNEL = 'cloudchannel'
|
||||
CLOUDIDENTITY_DEVICES = 'cloudidentitydevices'
|
||||
CLOUDIDENTITY_GROUPS = 'cloudidentitygroups'
|
||||
CLOUDIDENTITY_INBOUND_SSO = 'cloudidentityinboundsso'
|
||||
CLOUDIDENTITY_ORGUNITS = 'cloudidentityorgunits'
|
||||
CLOUDIDENTITY_ORGUNITS_BETA = 'cloudidentityorgunitsbeta'
|
||||
CLOUDIDENTITY_POLICY = 'cloudidentitypolicy'
|
||||
CLOUDIDENTITY_POLICY_BETA = 'cloudidentitypolicybeta'
|
||||
CLOUDIDENTITY_USERINVITATIONS = 'cloudidentityuserinvitations'
|
||||
CLOUDRESOURCEMANAGER = 'cloudresourcemanager'
|
||||
CLOUDRESOURCEMANAGERV1 = 'cloudresourcemanagerv1'
|
||||
CONTACTS = 'contacts'
|
||||
CONTACTDELEGATION = 'contactdelegation'
|
||||
DATATRANSFER = 'datatransfer'
|
||||
DIRECTORY = 'directory'
|
||||
DOCS = 'docs'
|
||||
DRIVE2 = 'drive2'
|
||||
DRIVE3 = 'drive3'
|
||||
DRIVETD = 'drivetd'
|
||||
DRIVEACTIVITY = 'driveactivity'
|
||||
DRIVELABELS = 'drivelabels'
|
||||
DRIVELABELS_ADMIN = 'drivelabelsadmin'
|
||||
DRIVELABELS_USER = 'drivelabelsuser'
|
||||
EMAIL_AUDIT = 'email-audit'
|
||||
FORMS = 'forms'
|
||||
GMAIL = 'gmail'
|
||||
GROUPSMIGRATION = 'groupsmigration'
|
||||
GROUPSSETTINGS = 'groupssettings'
|
||||
IAM = 'iam'
|
||||
IAM_CREDENTIALS = 'iamcredentials'
|
||||
KEEP = 'keep'
|
||||
LICENSING = 'licensing'
|
||||
LOOKERSTUDIO = 'datastudio'
|
||||
MEET_SPACES = 'meet'
|
||||
MEET_READONLY = 'meetreadonly'
|
||||
OAUTH2 = 'oauth2'
|
||||
ORGPOLICY = 'orgpolicy'
|
||||
PEOPLE = 'people'
|
||||
PEOPLE_DIRECTORY = 'peopledirectory'
|
||||
PEOPLE_OTHERCONTACTS = 'peopleothercontacts'
|
||||
PRINTERS = 'printers'
|
||||
PUBSUB = 'pubsub'
|
||||
REPORTS = 'reports'
|
||||
RESELLER = 'reseller'
|
||||
SEARCHCONSOLE = 'searchconsole'
|
||||
SERVICEACCOUNTLOOKUP = 'serviceaccountlookup'
|
||||
SERVICEMANAGEMENT = 'servicemanagement'
|
||||
SERVICEUSAGE = 'serviceusage'
|
||||
SHEETS = 'sheets'
|
||||
SHEETSTD = 'sheetstd'
|
||||
SITEVERIFICATION = 'siteVerification'
|
||||
STORAGE = 'storage'
|
||||
STORAGEREAD = 'storageread'
|
||||
STORAGEWRITE = 'storagewrite'
|
||||
TAGMANAGER = 'tagmanager'
|
||||
TAGMANAGER_USERS = 'tagmanagerusers'
|
||||
TASKS = 'tasks'
|
||||
VAULT = 'vault'
|
||||
YOUTUBE = 'youtube'
|
||||
#
|
||||
CHROMEVERSIONHISTORY_URL = 'https://versionhistory.googleapis.com/v1/chrome/platforms'
|
||||
DRIVE_SCOPE = 'https://www.googleapis.com/auth/drive'
|
||||
DRIVE_FILE_SCOPE = 'https://www.googleapis.com/auth/drive.file'
|
||||
DRIVE_READONLY_SCOPE = 'https://www.googleapis.com/auth/drive.readonly'
|
||||
GMAIL_SEND_SCOPE = 'https://www.googleapis.com/auth/gmail.send'
|
||||
GOOGLE_AUTH_PROVIDER_X509_CERT_URL = 'https://www.googleapis.com/oauth2/v1/certs'
|
||||
GOOGLE_OAUTH2_ENDPOINT = 'https://accounts.google.com/o/oauth2/v2/auth'
|
||||
GOOGLE_OAUTH2_TOKEN_ENDPOINT = 'https://oauth2.googleapis.com/token'
|
||||
CLOUD_PLATFORM_SCOPE = 'https://www.googleapis.com/auth/cloud-platform'
|
||||
IAM_SCOPE = 'https://www.googleapis.com/auth/iam'
|
||||
PEOPLE_SCOPE = 'https://www.googleapis.com/auth/contacts'
|
||||
STORAGE_READONLY_SCOPE = 'https://www.googleapis.com/auth/devstorage.read_only'
|
||||
STORAGE_READWRITE_SCOPE = 'https://www.googleapis.com/auth/devstorage.read_write'
|
||||
USERINFO_EMAIL_SCOPE = 'https://www.googleapis.com/auth/userinfo.email' # email
|
||||
USERINFO_PROFILE_SCOPE = 'https://www.googleapis.com/auth/userinfo.profile' # profile
|
||||
REQUIRED_SCOPES = [USERINFO_EMAIL_SCOPE, USERINFO_PROFILE_SCOPE]
|
||||
REQUIRED_SCOPES_SET = set(REQUIRED_SCOPES)
|
||||
NUM_CLIENT_SCOPES_ERROR_LIMIT = 48
|
||||
#
|
||||
JWT_APIS = {
|
||||
ACCESSCONTEXTMANAGER: [CLOUD_PLATFORM_SCOPE],
|
||||
CHAT: ['https://www.googleapis.com/auth/chat.bot'],
|
||||
CLOUDRESOURCEMANAGER: [CLOUD_PLATFORM_SCOPE],
|
||||
IAM: [IAM_SCOPE],
|
||||
ORGPOLICY: [CLOUD_PLATFORM_SCOPE],
|
||||
}
|
||||
#
|
||||
SCOPELESS_APIS = {
|
||||
CHROMEVERSIONHISTORY,
|
||||
OAUTH2,
|
||||
SERVICEACCOUNTLOOKUP,
|
||||
}
|
||||
#
|
||||
|
||||
# Scopes not in the discovery doc that are still valid for the API.
|
||||
EXTRA_SCOPES = {
|
||||
CLOUDRESOURCEMANAGER: ['https://www.googleapis.com/auth/cloudplatformfolders',
|
||||
'https://www.googleapis.com/auth/cloudplatformfolders.readonly',
|
||||
'https://www.googleapis.com/auth/cloudplatformprojects',
|
||||
'https://www.googleapis.com/auth/cloudplatformprojects.readonly',
|
||||
'https://www.googleapis.com/auth/cloudplatformorganizations',
|
||||
'https://www.googleapis.com/auth/cloudplatformorganizations.readonly',
|
||||
],
|
||||
VAULT: ['https://www.googleapis.com/auth/ediscovery', 'https://www.googleapis.com/auth/ediscovery.readonly'],
|
||||
}
|
||||
EXTRA_SCOPES[CLOUDRESOURCEMANAGERV1] = EXTRA_SCOPES[CLOUDRESOURCEMANAGER]
|
||||
|
||||
APIS_NEEDING_ACCESS_TOKEN = {
|
||||
CBCM: ['https://www.googleapis.com/auth/admin.directory.device.chromebrowsers']
|
||||
}
|
||||
#
|
||||
DEPRECATED_SCOPES = {
|
||||
'https://www.googleapis.com/auth/cloud-identity',
|
||||
'https://www.googleapis.com/auth/cloud-platform',
|
||||
'https://www.googleapis.com/auth/iam',
|
||||
}
|
||||
#
|
||||
REFRESH_PERM_ERRORS = [
|
||||
'invalid_grant: reauth related error (rapt_required)', # no way to reauth today
|
||||
'invalid_grant: Token has been expired or revoked',
|
||||
]
|
||||
|
||||
OAUTH2_TOKEN_ERRORS = [
|
||||
'access_denied',
|
||||
'access_denied: Requested client not authorized',
|
||||
'access_denied: Account restricted',
|
||||
'internal_failure: Backend Error',
|
||||
'internal_failure: None',
|
||||
'invalid_account: Forbidden',
|
||||
'invalid_grant',
|
||||
'invalid_grant: Bad Request',
|
||||
'invalid_grant: Invalid email or User ID',
|
||||
'invalid_grant: Not a valid email',
|
||||
'invalid_grant: Invalid JWT: No valid verifier found for issuer',
|
||||
'invalid_grant: reauth related error (invalid_rapt)',
|
||||
'invalid_grant: The account has been deleted',
|
||||
'invalid_request: Invalid impersonation prn email address'
|
||||
]
|
||||
OAUTH2_UNAUTHORIZED_ERRORS = [
|
||||
'unauthorized_client: Client is unauthorized to retrieve access tokens using this method',
|
||||
'unauthorized_client: Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested',
|
||||
'unauthorized_client: Unauthorized client or scope in request',
|
||||
]
|
||||
|
||||
PROJECT_APIS = [
|
||||
'accesscontextmanager.googleapis.com',
|
||||
'admin.googleapis.com',
|
||||
'alertcenter.googleapis.com',
|
||||
'analyticsadmin.googleapis.com',
|
||||
# 'audit.googleapis.com',
|
||||
'mybusinessaccountmanagement.googleapis.com',
|
||||
'calendar-json.googleapis.com',
|
||||
'chat.googleapis.com',
|
||||
'chromemanagement.googleapis.com',
|
||||
'chromepolicy.googleapis.com',
|
||||
'classroom.googleapis.com',
|
||||
'cloudchannel.googleapis.com',
|
||||
'cloudidentity.googleapis.com',
|
||||
'cloudresourcemanager.googleapis.com',
|
||||
'contacts.googleapis.com',
|
||||
'datastudio.googleapis.com',
|
||||
'docs.googleapis.com',
|
||||
'drive.googleapis.com',
|
||||
'driveactivity.googleapis.com',
|
||||
'drivelabels.googleapis.com',
|
||||
'forms.googleapis.com',
|
||||
'gmail.googleapis.com',
|
||||
'groupsmigration.googleapis.com',
|
||||
'groupssettings.googleapis.com',
|
||||
'iam.googleapis.com',
|
||||
'keep.googleapis.com',
|
||||
'licensing.googleapis.com',
|
||||
'meet.googleapis.com',
|
||||
'people.googleapis.com',
|
||||
'pubsub.googleapis.com',
|
||||
'reseller.googleapis.com',
|
||||
'searchconsole.googleapis.com',
|
||||
'sheets.googleapis.com',
|
||||
'siteverification.googleapis.com',
|
||||
'storage-api.googleapis.com',
|
||||
'tagmanager.googleapis.com',
|
||||
'tasks.googleapis.com',
|
||||
'vault.googleapis.com',
|
||||
'youtube.googleapis.com',
|
||||
]
|
||||
|
||||
_INFO = {
|
||||
ACCESSCONTEXTMANAGER: {'name': 'Access Context Manager API', 'version': 'v1', 'v2discovery': True},
|
||||
ALERTCENTER: {'name': 'AlertCenter API', 'version': 'v1beta1', 'v2discovery': True},
|
||||
ANALYTICS_ADMIN: {'name': 'Analytics Admin API', 'version': 'v1beta', 'v2discovery': True},
|
||||
BUSINESSACCOUNTMANAGEMENT: {'name': 'Business Account Management API', 'version': 'v1', 'v2discovery': True},
|
||||
CALENDAR: {'name': 'Calendar API', 'version': 'v3', 'v2discovery': True, 'mappedAPI': 'calendar-json'},
|
||||
CBCM: {'name': 'Chrome Browser Cloud Management API', 'version': 'v1.1beta1', 'v2discovery': True, 'localjson': True},
|
||||
CHAT: {'name': 'Chat API', 'version': 'v1', 'v2discovery': True},
|
||||
CHAT_CUSTOM_EMOJIS: {'name': 'Chat API - Custom Emojis', 'version': 'v1', 'v2discovery': True, 'mappedAPI': CHAT},
|
||||
CHAT_EVENTS: {'name': 'Chat API - Events', 'version': 'v1', 'v2discovery': True, 'mappedAPI': CHAT},
|
||||
CHAT_MEMBERSHIPS: {'name': 'Chat API - Memberships', 'version': 'v1', 'v2discovery': True, 'mappedAPI': CHAT},
|
||||
CHAT_MEMBERSHIPS_ADMIN: {'name': 'Chat API - Memberships Admin', 'version': 'v1', 'v2discovery': True, 'mappedAPI': CHAT},
|
||||
CHAT_MESSAGES: {'name': 'Chat API - Messages', 'version': 'v1', 'v2discovery': True, 'mappedAPI': CHAT},
|
||||
CHAT_SECTIONS: {'name': 'Chat API - User Sections', 'version': 'v1', 'v2discovery': True, 'mappedAPI': CHAT},
|
||||
CHAT_SPACES: {'name': 'Chat API - Spaces', 'version': 'v1', 'v2discovery': True, 'mappedAPI': CHAT},
|
||||
CHAT_SPACES_ADMIN: {'name': 'Chat API - Spaces Admin', 'version': 'v1', 'v2discovery': True, 'mappedAPI': CHAT},
|
||||
CHAT_SPACES_DELETE: {'name': 'Chat API - Spaces Delete', 'version': 'v1', 'v2discovery': True, 'mappedAPI': CHAT},
|
||||
CHAT_SPACES_DELETE_ADMIN: {'name': 'Chat API - Spaces Delete Admin', 'version': 'v1', 'v2discovery': True, 'mappedAPI': CHAT},
|
||||
CLASSROOM: {'name': 'Classroom API', 'version': 'v1', 'v2discovery': True},
|
||||
CHROMEMANAGEMENT: {'name': 'Chrome Management API', 'version': 'v1', 'v2discovery': True},
|
||||
CHROMEMANAGEMENT_APPDETAILS: {'name': 'Chrome Management API - AppDetails', 'version': 'v1', 'v2discovery': True, 'mappedAPI': CHROMEMANAGEMENT},
|
||||
CHROMEMANAGEMENT_TELEMETRY: {'name': 'Chrome Management API - Telemetry', 'version': 'v1', 'v2discovery': True, 'mappedAPI': CHROMEMANAGEMENT},
|
||||
CHROMEPOLICY: {'name': 'Chrome Policy API', 'version': 'v1', 'v2discovery': True},
|
||||
CHROMEVERSIONHISTORY: {'name': 'Chrome Version History API', 'version': 'v1', 'v2discovery': True},
|
||||
CLOUDCHANNEL: {'name': 'Cloud Channel API', 'version': 'v1', 'v2discovery': True},
|
||||
CLOUDIDENTITY_DEVICES: {'name': 'Cloud Identity API - Devices', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_GROUPS: {'name': 'Cloud Identity API - Groups', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_INBOUND_SSO: {'name': 'Cloud Identity API - Inbound SSO Settings', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_ORGUNITS: {'name': 'Cloud Identity API - OrgUnits', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_ORGUNITS_BETA: {'name': 'Cloud Identity API - OrgUnits Beta', 'version': 'v1beta1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_POLICY: {'name': 'Cloud Identity API - Policy', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_POLICY_BETA: {'name': 'Cloud Identity API - Policy Beta', 'version': 'v1beta1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_USERINVITATIONS: {'name': 'Cloud Identity API - User Invitations', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDRESOURCEMANAGER: {'name': 'Resource Manager API v3', 'version': 'v3', 'v2discovery': True},
|
||||
CLOUDRESOURCEMANAGERV1: {'name': 'Resource Manager API v1', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudresourcemanager'},
|
||||
CONTACTS: {'name': 'Contacts API', 'version': 'v3', 'v2discovery': False},
|
||||
CONTACTDELEGATION: {'name': 'Contact Delegation API', 'version': 'v1', 'v2discovery': True, 'localjson': True},
|
||||
DATATRANSFER: {'name': 'Data Transfer API', 'version': 'datatransfer_v1', 'v2discovery': True, 'mappedAPI': 'admin'},
|
||||
DIRECTORY: {'name': 'Directory API', 'version': 'directory_v1', 'v2discovery': True, 'mappedAPI': 'admin'},
|
||||
DOCS: {'name': 'Docs API', 'version': 'v1', 'v2discovery': True},
|
||||
DRIVE2: {'name': 'Drive API v2', 'version': 'v2', 'v2discovery': False, 'mappedAPI': 'drive'},
|
||||
DRIVE3: {'name': 'Drive API v3', 'version': 'v3', 'v2discovery': False, 'mappedAPI': 'drive'},
|
||||
DRIVETD: {'name': 'Drive API v3 - write todrive data', 'version': 'v3', 'v2discovery': False, 'mappedAPI': 'drive'},
|
||||
DRIVEACTIVITY: {'name': 'Drive Activity API v2', 'version': 'v2', 'v2discovery': True},
|
||||
DRIVELABELS_ADMIN: {'name': 'Drive Labels API - Admin', 'version': 'v2', 'v2discovery': True, 'mappedAPI': DRIVELABELS},
|
||||
DRIVELABELS_USER: {'name': 'Drive Labels API - User', 'version': 'v2', 'v2discovery': True, 'mappedAPI': DRIVELABELS},
|
||||
EMAIL_AUDIT: {'name': 'Email Audit API', 'version': 'v1', 'v2discovery': False},
|
||||
FORMS: {'name': 'Forms API', 'version': 'v1', 'v2discovery': True},
|
||||
GMAIL: {'name': 'Gmail API', 'version': 'v1', 'v2discovery': True},
|
||||
GROUPSMIGRATION: {'name': 'Groups Migration API', 'version': 'v1', 'v2discovery': True},
|
||||
GROUPSSETTINGS: {'name': 'Groups Settings API', 'version': 'v1', 'v2discovery': True},
|
||||
IAM: {'name': 'Identity and Access Management API', 'version': 'v1', 'v2discovery': True},
|
||||
IAM_CREDENTIALS: {'name': 'Identity and Access Management Credentials API', 'version': 'v1', 'v2discovery': True},
|
||||
KEEP: {'name': 'Keep API', 'version': 'v1', 'v2discovery': True},
|
||||
LICENSING: {'name': 'License Manager API', 'version': 'v1', 'v2discovery': True},
|
||||
LOOKERSTUDIO: {'name': 'Looker Studio API', 'version': 'v1', 'v2discovery': True, 'localjson': True},
|
||||
MEET_SPACES: {'name': 'Meet API - Manage/Display Meeting Spaces', 'version': 'v2', 'v2discovery': True},
|
||||
MEET_READONLY: {'name': 'Meet API - Read Meeting Spaces metadata', 'version': 'v2', 'v2discovery': True, 'mappedAPI': MEET_SPACES},
|
||||
OAUTH2: {'name': 'OAuth2 API', 'version': 'v2', 'v2discovery': False},
|
||||
ORGPOLICY: {'name': 'Organization Policy API', 'version': 'v2', 'v2discovery': True},
|
||||
PEOPLE: {'name': 'People API', 'version': 'v1', 'v2discovery': True},
|
||||
PEOPLE_DIRECTORY: {'name': 'People Directory API', 'version': 'v1', 'v2discovery': True, 'mappedAPI': PEOPLE},
|
||||
PEOPLE_OTHERCONTACTS: {'name': 'People API - Other Contacts', 'version': 'v1', 'v2discovery': True, 'mappedAPI': PEOPLE},
|
||||
PRINTERS: {'name': 'Directory API - Printers', 'version': 'directory_v1', 'v2discovery': True, 'mappedAPI': 'admin'},
|
||||
PUBSUB: {'name': 'Pub / Sub API', 'version': 'v1', 'v2discovery': True},
|
||||
REPORTS: {'name': 'Reports API', 'version': 'reports_v1', 'v2discovery': True, 'mappedAPI': 'admin'},
|
||||
RESELLER: {'name': 'Reseller API', 'version': 'v1', 'v2discovery': True},
|
||||
SEARCHCONSOLE: {'name': 'Search Console API', 'version': 'v1', 'v2discovery': True},
|
||||
SERVICEACCOUNTLOOKUP: {'name': 'Service Account Lookup pseudo-API', 'version': 'v1', 'v2discovery': True, 'localjson': True},
|
||||
SERVICEMANAGEMENT: {'name': 'Service Management API', 'version': 'v1', 'v2discovery': True},
|
||||
SERVICEUSAGE: {'name': 'Service Usage API', 'version': 'v1', 'v2discovery': True},
|
||||
SHEETS: {'name': 'Sheets API', 'version': 'v4', 'v2discovery': True},
|
||||
SHEETSTD: {'name': 'Sheets API - write todrive data', 'version': 'v4', 'v2discovery': True, 'mappedAPI': SHEETS},
|
||||
SITEVERIFICATION: {'name': 'Site Verification API', 'version': 'v1', 'v2discovery': True},
|
||||
STORAGE: {'name': 'Cloud Storage API', 'version': 'v1', 'v2discovery': True},
|
||||
STORAGEREAD: {'name': 'Cloud Storage API - Read', 'version': 'v1', 'v2discovery': True, 'mappedAPI': STORAGE},
|
||||
STORAGEWRITE: {'name': 'Cloud Storage API - Write', 'version': 'v1', 'v2discovery': True, 'mappedAPI': STORAGE},
|
||||
TAGMANAGER: {'name': 'Tag Manager API - Accounts, Containers, Workspaces, Tags', 'version': 'v2', 'v2discovery': True},
|
||||
TAGMANAGER_USERS: {'name': 'Tag Manager API - Users', 'version': 'v2', 'v2discovery': True, 'mappedAPI': TAGMANAGER},
|
||||
TASKS: {'name': 'Tasks API', 'version': 'v1', 'v2discovery': True},
|
||||
VAULT: {'name': 'Vault API', 'version': 'v1', 'v2discovery': True},
|
||||
YOUTUBE: {'name': 'Youtube API', 'version': 'v3', 'v2discovery': True},
|
||||
}
|
||||
|
||||
READONLY = ['readonly',]
|
||||
|
||||
_CLIENT_SCOPES = [
|
||||
{'name': 'Calendar API',
|
||||
'api': CALENDAR,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/calendar'},
|
||||
{'name': 'Chrome Browser Cloud Management API',
|
||||
'api': CBCM,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/admin.directory.device.chromebrowsers'},
|
||||
{'name': 'Chrome Management API - readonly',
|
||||
'api': CHROMEMANAGEMENT,
|
||||
'scope': 'https://www.googleapis.com/auth/chrome.management.reports.readonly'},
|
||||
{'name': 'Chrome Management API - AppDetails readonly',
|
||||
'api': CHROMEMANAGEMENT_APPDETAILS,
|
||||
'scope': 'https://www.googleapis.com/auth/chrome.management.appdetails.readonly'},
|
||||
{'name': 'Chrome Management API - Profiles',
|
||||
'api': CHROMEMANAGEMENT_CHROMEPROFILES,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/chrome.management.profiles'},
|
||||
{'name': 'Chrome Management API - Telemetry readonly',
|
||||
'api': CHROMEMANAGEMENT_TELEMETRY,
|
||||
'scope': 'https://www.googleapis.com/auth/chrome.management.telemetry.readonly'},
|
||||
{'name': 'Chrome Policy API',
|
||||
'api': CHROMEPOLICY,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/chrome.management.policy'},
|
||||
{'name': 'Chrome Printer Management API',
|
||||
'api': PRINTERS,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/admin.chrome.printers'},
|
||||
{'name': 'Chrome Version History API',
|
||||
'api': CHROMEVERSIONHISTORY,
|
||||
'scope': ''},
|
||||
{'name': 'Classroom API - Courses',
|
||||
'api': CLASSROOM,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.courses'},
|
||||
{'name': 'Classroom API - Course Announcements',
|
||||
'api': CLASSROOM,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.announcements'},
|
||||
{'name': 'Classroom API - Course Topics',
|
||||
'api': CLASSROOM,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.topics'},
|
||||
{'name': 'Classroom API - Course Work/Materials',
|
||||
'api': CLASSROOM,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.courseworkmaterials'},
|
||||
{'name': 'Classroom API - Course Work/Submissions',
|
||||
'api': CLASSROOM,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.coursework.students'},
|
||||
{'name': 'Classroom API - Student Guardians',
|
||||
'api': CLASSROOM,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.guardianlinks.students'},
|
||||
{'name': 'Classroom API - Profile Emails',
|
||||
'api': CLASSROOM,
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.profile.emails'},
|
||||
{'name': 'Classroom API - Profile Photos',
|
||||
'api': CLASSROOM,
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.profile.photos'},
|
||||
{'name': 'Classroom API - Rosters',
|
||||
'api': CLASSROOM,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.rosters'},
|
||||
{'name': 'Cloud Channel API',
|
||||
'api': CLOUDCHANNEL,
|
||||
'subscopes': READONLY,
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/apps.order'},
|
||||
{'name': 'Cloud Identity API - Groups',
|
||||
'api': CLOUDIDENTITY_GROUPS,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/cloud-identity.groups'},
|
||||
{'name': 'Cloud Identity API - Inbound SSO Settings',
|
||||
'api': CLOUDIDENTITY_INBOUND_SSO,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/cloud-identity.inboundsso'},
|
||||
{'name': 'Cloud Identity API - OrgUnits Beta',
|
||||
'api': CLOUDIDENTITY_ORGUNITS_BETA,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/cloud-identity.orgunits'},
|
||||
{'name': 'Cloud Identity API - Policy',
|
||||
'api': CLOUDIDENTITY_POLICY,
|
||||
'subscopes': READONLY,
|
||||
'roByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'},
|
||||
{'name': 'Cloud Identity API - Policy Beta',
|
||||
'api': CLOUDIDENTITY_POLICY_BETA,
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'},
|
||||
{'name': 'Cloud Identity API - User Invitations',
|
||||
'api': CLOUDIDENTITY_USERINVITATIONS,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/cloud-identity.userinvitations'},
|
||||
{'name': 'Cloud Storage API (Read Only, Vault/Takeout Download, Cloud Storage)',
|
||||
'api': STORAGEREAD,
|
||||
'offByDefault': True,
|
||||
'scope': STORAGE_READONLY_SCOPE},
|
||||
{'name': 'Cloud Storage API (Read/Write, Vault/Takeout Copy/Download, Cloud Storage)',
|
||||
'api': STORAGEWRITE,
|
||||
'offByDefault': True,
|
||||
'scope': STORAGE_READWRITE_SCOPE},
|
||||
{'name': 'Contacts API - Domain Shared Contacts',
|
||||
'api': CONTACTS,
|
||||
'scope': 'https://www.google.com/m8/feeds'},
|
||||
{'name': 'Contact Delegation API',
|
||||
'api': CONTACTDELEGATION,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/admin.contact.delegation'},
|
||||
{'name': 'Data Transfer API',
|
||||
'api': DATATRANSFER,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/admin.datatransfer'},
|
||||
{'name': 'Directory API - Chrome OS Devices',
|
||||
'api': DIRECTORY,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/admin.directory.device.chromeos'},
|
||||
{'name': 'Directory API - Customers',
|
||||
'api': DIRECTORY,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/admin.directory.customer'},
|
||||
{'name': 'Directory API - Domains',
|
||||
'api': DIRECTORY,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/admin.directory.domain'},
|
||||
{'name': 'Directory API - Groups',
|
||||
'api': DIRECTORY,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/admin.directory.group'},
|
||||
{'name': 'Directory API - Mobile Devices Directory',
|
||||
'api': DIRECTORY,
|
||||
'subscopes': ['readonly', 'actiononly'],
|
||||
'scope': 'https://www.googleapis.com/auth/admin.directory.device.mobile'},
|
||||
{'name': 'Directory API - Organizational Units',
|
||||
'api': DIRECTORY,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/admin.directory.orgunit'},
|
||||
{'name': 'Directory API - Resource Calendars',
|
||||
'api': DIRECTORY,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/admin.directory.resource.calendar'},
|
||||
{'name': 'Directory API - Roles',
|
||||
'api': DIRECTORY,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/admin.directory.rolemanagement'},
|
||||
{'name': 'Directory API - User Schemas',
|
||||
'api': DIRECTORY,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/admin.directory.userschema'},
|
||||
{'name': 'Directory API - User Security',
|
||||
'api': DIRECTORY,
|
||||
'scope': 'https://www.googleapis.com/auth/admin.directory.user.security'},
|
||||
{'name': 'Directory API - Users',
|
||||
'api': DIRECTORY,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/admin.directory.user'},
|
||||
{'name': 'Email Audit API',
|
||||
'api': EMAIL_AUDIT,
|
||||
'offByDefault': True,
|
||||
'scope': 'https://apps-apis.google.com/a/feeds/compliance/audit/'},
|
||||
{'name': 'Groups Migration API',
|
||||
'api': GROUPSMIGRATION,
|
||||
'scope': 'https://www.googleapis.com/auth/apps.groups.migration'},
|
||||
{'name': 'Groups Settings API',
|
||||
'api': GROUPSSETTINGS,
|
||||
'scope': 'https://www.googleapis.com/auth/apps.groups.settings'},
|
||||
{'name': 'License Manager API',
|
||||
'api': LICENSING,
|
||||
'scope': 'https://www.googleapis.com/auth/apps.licensing'},
|
||||
{'name': 'People Directory API - readonly',
|
||||
'api': PEOPLE_DIRECTORY,
|
||||
'scope': 'https://www.googleapis.com/auth/directory.readonly'},
|
||||
{'name': 'People API',
|
||||
'api': PEOPLE,
|
||||
'subscopes': READONLY,
|
||||
'scope': PEOPLE_SCOPE},
|
||||
{'name': 'Pub / Sub API',
|
||||
'api': PUBSUB,
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/pubsub'},
|
||||
{'name': 'Reports API - Audit Reports readonly',
|
||||
'api': REPORTS,
|
||||
'scope': 'https://www.googleapis.com/auth/admin.reports.audit.readonly'},
|
||||
{'name': 'Reports API - Usage Reports readonly',
|
||||
'api': REPORTS,
|
||||
'scope': 'https://www.googleapis.com/auth/admin.reports.usage.readonly'},
|
||||
{'name': 'Reseller API',
|
||||
'api': RESELLER,
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/apps.order'},
|
||||
{'name': 'Resource Manager API - Organizations readonly',
|
||||
'api': CLOUDRESOURCEMANAGER,
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/cloudplatformorganizations.readonly'},
|
||||
{'name': 'Resource Manager API - Projects readonly',
|
||||
'api': CLOUDRESOURCEMANAGER,
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/cloudplatformprojects.readonly'},
|
||||
{'name': 'Service Account Lookup pseudo-API',
|
||||
'api': SERVICEACCOUNTLOOKUP,
|
||||
'scope': ''},
|
||||
{'name': 'Site Verification API',
|
||||
'api': SITEVERIFICATION,
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/siteverification'},
|
||||
{'name': 'Vault API',
|
||||
'api': VAULT,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/ediscovery'},
|
||||
]
|
||||
|
||||
_COMMANDDATA_CLIENT_SCOPES = [
|
||||
{'name': 'Drive API - commanddata_clientaccess',
|
||||
'api': DRIVE3,
|
||||
'scope': DRIVE_READONLY_SCOPE},
|
||||
{'name': 'Sheets API - commanddata_clientaccess readonly',
|
||||
'api': SHEETS,
|
||||
'scope': 'https://www.googleapis.com/auth/spreadsheets.readonly'},
|
||||
]
|
||||
|
||||
_TODRIVE_CLIENT_SCOPES = [
|
||||
{'name': 'Drive API - todrive_clientaccess',
|
||||
'api': DRIVE3,
|
||||
'scope': DRIVE_SCOPE},
|
||||
{'name': 'Drive File API - todrive_clientaccess',
|
||||
'api': DRIVE3,
|
||||
'scope': DRIVE_FILE_SCOPE},
|
||||
{'name': 'Gmail API - todrive_clientaccess',
|
||||
'api': GMAIL,
|
||||
'scope': GMAIL_SEND_SCOPE},
|
||||
{'name': 'Sheets API - todrive_clientaccess',
|
||||
'api': SHEETS,
|
||||
'scope': 'https://www.googleapis.com/auth/spreadsheets'},
|
||||
]
|
||||
|
||||
OAUTH2SA_SCOPES = 'us_scopes'
|
||||
|
||||
_SVCACCT_SCOPES = [
|
||||
{'name': 'AlertCenter API',
|
||||
'api': ALERTCENTER,
|
||||
'scope': 'https://www.googleapis.com/auth/apps.alerts'},
|
||||
{'name': 'Analytics Admin API - readonly',
|
||||
'api': ANALYTICS_ADMIN,
|
||||
'scope': 'https://www.googleapis.com/auth/analytics.readonly'},
|
||||
{'name': 'Business Account Management API',
|
||||
'api': BUSINESSACCOUNTMANAGEMENT,
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/business.manage'},
|
||||
{'name': 'Calendar API',
|
||||
'api': CALENDAR,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/calendar'},
|
||||
{'name': 'Chat API - Custom Emojis',
|
||||
'api': CHAT_CUSTOM_EMOJIS,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/chat.customemojis'},
|
||||
{'name': 'Chat API - Memberships',
|
||||
'api': CHAT_MEMBERSHIPS,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/chat.memberships'},
|
||||
{'name': 'Chat API - Memberships Admin',
|
||||
'api': CHAT_MEMBERSHIPS_ADMIN,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/chat.admin.memberships'},
|
||||
{'name': 'Chat API - Messages',
|
||||
'api': CHAT_MESSAGES,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/chat.messages'},
|
||||
{'name': 'Chat API - User Sections',
|
||||
'api': CHAT_SECTIONS,
|
||||
'offByDefault': True,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/chat.users.sections'},
|
||||
{'name': 'Chat API - Spaces',
|
||||
'api': CHAT_SPACES,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/chat.spaces'},
|
||||
{'name': 'Chat API - Spaces Admin',
|
||||
'api': CHAT_SPACES_ADMIN,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/chat.admin.spaces'},
|
||||
{'name': 'Chat API - Spaces Delete',
|
||||
'api': CHAT_SPACES_DELETE,
|
||||
'scope': 'https://www.googleapis.com/auth/chat.delete'},
|
||||
{'name': 'Chat API - Spaces Delete Admin',
|
||||
'api': CHAT_SPACES_DELETE_ADMIN,
|
||||
'scope': 'https://www.googleapis.com/auth/chat.admin.delete'},
|
||||
{'name': 'Classroom API - Course Announcements',
|
||||
'api': CLASSROOM,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.announcements'},
|
||||
{'name': 'Classroom API - Course Topics',
|
||||
'api': CLASSROOM,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.topics'},
|
||||
{'name': 'Classroom API - Course Work/Materials',
|
||||
'api': CLASSROOM,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.courseworkmaterials'},
|
||||
{'name': 'Classroom API - Course Work/Submissions',
|
||||
'api': CLASSROOM,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.coursework.students'},
|
||||
{'name': 'Classroom API - Profile Emails',
|
||||
'api': CLASSROOM,
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.profile.emails'},
|
||||
{'name': 'Classroom API - Profile Photos',
|
||||
'api': CLASSROOM,
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.profile.photos'},
|
||||
{'name': 'Classroom API - Rosters',
|
||||
'api': CLASSROOM,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.rosters'},
|
||||
{'name': 'Cloud Identity Devices API',
|
||||
'api': CLOUDIDENTITY_DEVICES,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/cloud-identity.devices'},
|
||||
# {'name': 'Cloud Identity API - Policy',
|
||||
# 'api': CLOUDIDENTITY_POLICY,
|
||||
# 'subscopes': READONLY,
|
||||
# 'roByDefault': True,
|
||||
# 'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'},
|
||||
# {'name': 'Cloud Identity API - Policy Beta',
|
||||
# 'api': CLOUDIDENTITY_POLICY_BETA,
|
||||
# 'offByDefault': True,
|
||||
# 'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'},
|
||||
# {'name': 'Cloud Identity User Invitations API',
|
||||
# 'api': CLOUDIDENTITY_USERINVITATIONS,
|
||||
# 'subscopes': READONLY,
|
||||
# 'scope': 'https://www.googleapis.com/auth/cloud-identity'},
|
||||
# {'name': 'Contacts API - Users',
|
||||
# 'api': CONTACTS,
|
||||
# 'scope': 'https://www.google.com/m8/feeds'},
|
||||
{'name': 'Drive API',
|
||||
'api': DRIVE3,
|
||||
'subscopes': READONLY,
|
||||
'scope': DRIVE_SCOPE},
|
||||
{'name': 'Drive Activity API v2 - must pair with Drive API',
|
||||
'api': DRIVEACTIVITY,
|
||||
'scope': [DRIVE_READONLY_SCOPE,
|
||||
'https://www.googleapis.com/auth/drive.activity']},
|
||||
{'name': 'Drive Labels API - Admin',
|
||||
'api': DRIVELABELS_ADMIN,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/drive.admin.labels'},
|
||||
{'name': 'Drive Labels API - User',
|
||||
'api': DRIVELABELS_USER,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/drive.labels'},
|
||||
{'name': 'Docs API',
|
||||
'api': DOCS,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/documents'},
|
||||
{'name': 'Forms API - must pair with Drive API',
|
||||
'api': FORMS,
|
||||
'scope': [DRIVE_READONLY_SCOPE,
|
||||
'https://www.googleapis.com/auth/forms.body',
|
||||
'https://www.googleapis.com/auth/forms.responses.readonly']},
|
||||
{'name': 'Gmail API - Full Access (Labels, Messages)',
|
||||
'api': GMAIL,
|
||||
'scope': 'https://mail.google.com/'},
|
||||
{'name': 'Gmail API - Full Access (Labels, Messages) except delete message',
|
||||
'api': GMAIL,
|
||||
'scope': 'https://www.googleapis.com/auth/gmail.modify'},
|
||||
{'name': 'Gmail API - Basic Settings (Filters, IMAP, Language, POP, Vacation) - read/write, Sharing Settings (Delegates, Forwarding, SendAs) - read',
|
||||
'api': GMAIL,
|
||||
'scope': 'https://www.googleapis.com/auth/gmail.settings.basic'},
|
||||
{'name': 'Gmail API - Sharing Settings (Delegates, Forwarding, SendAs) - write',
|
||||
'api': GMAIL,
|
||||
'scope': 'https://www.googleapis.com/auth/gmail.settings.sharing'},
|
||||
# {'name': 'Identity and Access Management API',
|
||||
# 'api': IAM,
|
||||
# 'offByDefault': True,
|
||||
# 'scope': CLOUD_PLATFORM_SCOPE},
|
||||
{'name': 'Keep API',
|
||||
'api': KEEP,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/keep'},
|
||||
{'name': 'Looker Studio API',
|
||||
'api': LOOKERSTUDIO,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/datastudio'},
|
||||
{'name': 'Meet API - Manage/Display Meeting Spaces',
|
||||
'api': MEET_SPACES,
|
||||
'scope': ['https://www.googleapis.com/auth/meetings.space.created',
|
||||
'https://www.googleapis.com/auth/meetings.space.settings']},
|
||||
{'name': 'Meet API - Read Meeting Spaces metadata readonly',
|
||||
'api': MEET_READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/meetings.space.readonly'},
|
||||
{'name': 'OAuth2 API',
|
||||
'api': OAUTH2,
|
||||
'scope': USERINFO_PROFILE_SCOPE},
|
||||
{'name': 'People API',
|
||||
'api': PEOPLE,
|
||||
'subscopes': READONLY,
|
||||
'scope': PEOPLE_SCOPE},
|
||||
{'name': 'People Directory API - readonly',
|
||||
'api': PEOPLE_DIRECTORY,
|
||||
'scope': 'https://www.googleapis.com/auth/directory.readonly'},
|
||||
{'name': 'People API - Other Contacts - readonly',
|
||||
'api': PEOPLE_OTHERCONTACTS,
|
||||
'scope': 'https://www.googleapis.com/auth/contacts.other.readonly'},
|
||||
{'name': 'Search Console API - readonly',
|
||||
'api': SEARCHCONSOLE,
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/webmasters.readonly'},
|
||||
{'name': 'Sheets API',
|
||||
'api': SHEETS,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/spreadsheets'},
|
||||
{'name': 'Site Verification API',
|
||||
'api': SITEVERIFICATION,
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/siteverification'},
|
||||
{'name': 'Tag Manager API - Accounts, Containers, Workspaces, Tags - readonly',
|
||||
'api': TAGMANAGER,
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/tagmanager.readonly'},
|
||||
{'name': 'Tag Manager API - Users',
|
||||
'api': TAGMANAGER_USERS,
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/tagmanager.manage.users'},
|
||||
{'name': 'Tasks API',
|
||||
'api': TASKS,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/tasks'},
|
||||
{'name': 'Youtube API - readonly',
|
||||
'api': YOUTUBE,
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/youtube.readonly'},
|
||||
]
|
||||
|
||||
_SVCACCT_SPECIAL_SCOPES = [
|
||||
{'name': 'Drive API - write todrive data - has access to all Drive',
|
||||
'api': DRIVETD,
|
||||
'offByDefault': True,
|
||||
'scope': DRIVE_SCOPE},
|
||||
{'name': 'Gmail API - Full Access - readonly',
|
||||
'api': GMAIL,
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/gmail.readonly'},
|
||||
{'name': 'Gmail API - Send Messages - including todrive',
|
||||
'api': GMAIL,
|
||||
'offByDefault': True,
|
||||
'scope': GMAIL_SEND_SCOPE},
|
||||
{'name': 'Sheets API - write todrive data - has access to all Sheets',
|
||||
'api': SHEETSTD,
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/spreadsheets'},
|
||||
]
|
||||
|
||||
_USER_SVCACCT_ONLY_SCOPES = [
|
||||
{'name': 'Groups Migration API',
|
||||
'api': GROUPSMIGRATION,
|
||||
'scope': 'https://www.googleapis.com/auth/apps.groups.migration'},
|
||||
]
|
||||
|
||||
def getAPIName(api):
|
||||
return _INFO[api]['name']
|
||||
|
||||
def getVersion(api):
|
||||
version = _INFO[api]['version']
|
||||
v2discovery = _INFO[api]['v2discovery']
|
||||
api = _INFO[api].get('mappedAPI', api)
|
||||
return (api, version, v2discovery)
|
||||
|
||||
def getAPIsList():
|
||||
apisList = set()
|
||||
for api, value in _INFO.items():
|
||||
apisList.add(value.get('mappedAPI', api))
|
||||
return apisList
|
||||
|
||||
def getClientScopesSet(api):
|
||||
return {scope['scope'] for scope in _CLIENT_SCOPES if scope['api'] == api}
|
||||
|
||||
def getClientScopesList(commanddataClientAccess, todriveClientAccess):
|
||||
caScopes = _CLIENT_SCOPES[:]
|
||||
if commanddataClientAccess:
|
||||
caScopes.extend(_COMMANDDATA_CLIENT_SCOPES)
|
||||
if todriveClientAccess:
|
||||
caScopes.extend(_TODRIVE_CLIENT_SCOPES)
|
||||
return sorted(caScopes, key=lambda k: k['name'])
|
||||
|
||||
def getClientScopesURLs(commanddataClientAccess, todriveClientAccess):
|
||||
caScopes = _CLIENT_SCOPES[:]
|
||||
if commanddataClientAccess:
|
||||
caScopes.extend(_COMMANDDATA_CLIENT_SCOPES)
|
||||
if todriveClientAccess:
|
||||
caScopes.extend(_TODRIVE_CLIENT_SCOPES)
|
||||
return sorted({scope['scope'] for scope in _CLIENT_SCOPES})
|
||||
|
||||
def getSvcAcctScopeAPI(uscope):
|
||||
for scope in _SVCACCT_SCOPES:
|
||||
if uscope == scope['scope'] or (uscope.endswith('.readonly') and 'readonly' in scope.get('subscopes', [])):
|
||||
return scope['api']
|
||||
return None
|
||||
|
||||
def getSvcAcctScopes(userServiceAccountAccessOnly, svcAcctSpecialScopes):
|
||||
saScopes = [scope['scope'] for scope in _SVCACCT_SCOPES]
|
||||
if userServiceAccountAccessOnly:
|
||||
saScopes.extend([scope['scope'] for scope in _USER_SVCACCT_ONLY_SCOPES])
|
||||
if svcAcctSpecialScopes:
|
||||
saScopes.extend([scope['scope'] for scope in _SVCACCT_SPECIAL_SCOPES])
|
||||
return saScopes
|
||||
|
||||
def getSvcAcctScopesList(userServiceAccountAccessOnly, svcAcctSpecialScopes):
|
||||
saScopes = _SVCACCT_SCOPES[:]
|
||||
if userServiceAccountAccessOnly:
|
||||
saScopes.extend(_USER_SVCACCT_ONLY_SCOPES)
|
||||
if svcAcctSpecialScopes:
|
||||
saScopes.extend(_SVCACCT_SPECIAL_SCOPES)
|
||||
return sorted(saScopes, key=lambda k: k['name'])
|
||||
|
||||
def hasLocalJSON(api):
|
||||
return _INFO[api].get('localjson', False)
|
||||
|
||||
def findAPIforScope(scopesList):
|
||||
def checkScopeMatch(scope, cscope):
|
||||
if cscope['scope'] == scope:
|
||||
requiredAPIs.append(cscope['name'])
|
||||
return True
|
||||
if 'readonly' in cscope.get('subscopes', []) and cscope['scope']+'.readonly' == scope:
|
||||
requiredAPIs.append(cscope['name']+' (supports readonly)')
|
||||
return True
|
||||
return False
|
||||
|
||||
requiredAPIs = []
|
||||
for scope in scopesList:
|
||||
for cscope in _CLIENT_SCOPES:
|
||||
if checkScopeMatch(scope, cscope):
|
||||
break
|
||||
else:
|
||||
for cscope in _SVCACCT_SCOPES:
|
||||
if checkScopeMatch(scope, cscope):
|
||||
break
|
||||
if not requiredAPIs:
|
||||
requiredAPIs = scopesList
|
||||
return ' or '.join(requiredAPIs)
|
||||
@@ -1,653 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2026 Ross Scroggs All Rights Reserved.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""GAM gam.cfg variables
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
TRUE = 'true'
|
||||
FALSE = 'false'
|
||||
DEFAULT_CHARSET = 'utf-8'
|
||||
MY_CUSTOMER = 'my_customer'
|
||||
NEVER = 'Never'
|
||||
TLS_CHOICE_MAP = {
|
||||
'': '',
|
||||
'tlsv1_2': 'TLSv1_2', 'tlsv1.2': 'TLSv1_2',
|
||||
'tlsv1_3': 'TLSv1_3', 'tlsv1.3': 'TLSv1_3',
|
||||
}
|
||||
|
||||
FN_CACERTS_PEM = 'cacerts.pem'
|
||||
FN_CLIENT_SECRETS_JSON = 'client_secrets.json'
|
||||
FN_EXTRA_ARGS_TXT = 'extra-args.txt'
|
||||
FN_OAUTH2_TXT = 'oauth2.txt'
|
||||
FN_OAUTH2SERVICE_JSON = 'oauth2service.json'
|
||||
|
||||
# Global variables defined in gam.cfg
|
||||
|
||||
# The following XXX constants are the names of the items in gam.cfg
|
||||
# When retrieving lists of Google Drive activities from API, how many should be retrieved in each chunk
|
||||
ACTIVITY_MAX_RESULTS = 'activity_max_results'
|
||||
# Admin email address, required when enable_dasa is true, overrides oauth2.txt value otherwise
|
||||
ADMIN_EMAIL = 'admin_email'
|
||||
# Check if API calls rate exceeds limit
|
||||
API_CALLS_RATE_CHECK = 'api_calls_rate_check'
|
||||
# API calls per 100 seconds limit
|
||||
API_CALLS_RATE_LIMIT = 'api_calls_rate_limit'
|
||||
# API calls tries limit
|
||||
API_CALLS_TRIES_LIMIT = 'api_calls_tries_limit'
|
||||
# Automatically generate gam batch command if number of users specified in gam users xxx command exceeds this number
|
||||
# Default: 0, do not automatically generate gam batch commands
|
||||
AUTO_BATCH_MIN = 'auto_batch_min'
|
||||
# When bailing on internal errors, how many total tries should be performed
|
||||
BAIL_ON_INTERNAL_ERROR_TRIES = 'bail_on_internal_error_tries'
|
||||
# When processing items in batches, how many should be processed in each batch
|
||||
BATCH_SIZE = 'batch_size'
|
||||
# Location of cacerts.pem for API calls
|
||||
CACERTS_PEM = 'cacerts_pem'
|
||||
# GAM cache directory
|
||||
CACHE_DIR = 'cache_dir'
|
||||
# GAM cache discovery only. If no_cache is False, only API discovery calls will be cached
|
||||
CACHE_DISCOVERY_ONLY = 'cache_discovery_only'
|
||||
# Channel custmerId from gam.cfg
|
||||
CHANNEL_CUSTOMER_ID = 'channel_customer_id'
|
||||
# Character set of batch, csv, data files
|
||||
CHARSET = 'charset'
|
||||
# When retrieving lists of Chat items from API, how many should be retrieved in each chunk
|
||||
CHAT_MAX_RESULTS = 'chat_max_results'
|
||||
# When retrieving lists of Google Classroom items from API, how many should be retrieved in each chunk
|
||||
CLASSROOM_MAX_RESULTS = 'classroom_max_results'
|
||||
# Path to client_secrets.json
|
||||
CLIENT_SECRETS_JSON = 'client_secrets_json'
|
||||
# Allowed clock skew in seconds
|
||||
CLOCK_SKEW_IN_SECONDS = 'clock_skew_in_seconds'
|
||||
# Command logging filename
|
||||
CMDLOG = 'cmdlog'
|
||||
# Bogus Command logging maximum number of backup log files
|
||||
CMDLOG_MAX__BACKUPS = 'cmdlog_max__backups'
|
||||
# Command logging maximum number of backup log files
|
||||
CMDLOG_MAX_BACKUPS = 'cmdlog_max_backups'
|
||||
# Command logging max kilo bytes per log file
|
||||
CMDLOG_MAX_KILO_BYTES = 'cmdlog_max_kilo_bytes'
|
||||
# Use client access for command data from Google Docs/Sheets
|
||||
COMMANDDATA_CLIENTACCESS = 'commanddata_clientaccess'
|
||||
# GAM config directory containing client_secrets.json, oauth2.txt, oauth2service.json, extra_args.txt
|
||||
CONFIG_DIR = 'config_dir'
|
||||
# When retrieving lists of Google Contacts from API, how many should be retrieved in each chunk
|
||||
CONTACT_MAX_RESULTS = 'contact_max_results'
|
||||
# Column delimiter in CSV input file
|
||||
CSV_INPUT_COLUMN_DELIMITER = 'csv_input_column_delimiter'
|
||||
# No escape character in CSV input file
|
||||
CSV_INPUT_NO_ESCAPE_CHAR = 'csv_input_no_escape_char'
|
||||
# Quote character in CSV input file
|
||||
CSV_INPUT_QUOTE_CHAR = 'csv_input_quote_char'
|
||||
# Filter for input column values
|
||||
CSV_INPUT_ROW_FILTER = 'csv_input_row_filter'
|
||||
# Mode (and|or) for input column values
|
||||
CSV_INPUT_ROW_FILTER_MODE = 'csv_input_row_filter_mode'
|
||||
# Filter for input column drop values
|
||||
CSV_INPUT_ROW_DROP_FILTER = 'csv_input_row_drop_filter'
|
||||
# Mode (and|or) for input column drop values
|
||||
CSV_INPUT_ROW_DROP_FILTER_MODE = 'csv_input_row_drop_filter_mode'
|
||||
# Limit number of input rows
|
||||
CSV_INPUT_ROW_LIMIT = 'csv_input_row_limit'
|
||||
# Convert newlines in text fields to "\n" in CSV output file
|
||||
CSV_OUTPUT_CONVERT_CR_NL = 'csv_output_convert_cr_nl'
|
||||
# Column delimiter in CSV output file
|
||||
CSV_OUTPUT_COLUMN_DELIMITER = 'csv_output_column_delimiter'
|
||||
# No escape character in CSV output file
|
||||
CSV_OUTPUT_NO_ESCAPE_CHAR = 'csv_output_no_escape_char'
|
||||
# Field list delimiter in CSV output file
|
||||
CSV_OUTPUT_FIELD_DELIMITER = 'csv_output_field_delimiter'
|
||||
# Filter for output column headers
|
||||
CSV_OUTPUT_HEADER_FILTER = 'csv_output_header_filter'
|
||||
# Filter for output column headers to drop
|
||||
CSV_OUTPUT_HEADER_DROP_FILTER = 'csv_output_header_drop_filter'
|
||||
# Force output column headers
|
||||
CSV_OUTPUT_HEADER_FORCE = 'csv_output_header_force'
|
||||
# Orde output column headers
|
||||
CSV_OUTPUT_HEADER_ORDER = 'csv_output_header_order'
|
||||
# Required output column headers
|
||||
CSV_OUTPUT_HEADER_REQUIRED = 'csv_output_header_required'
|
||||
# Line terminator in CSV output file
|
||||
CSV_OUTPUT_LINE_TERMINATOR = 'csv_output_line_terminator'
|
||||
# Quote character in CSV output file
|
||||
CSV_OUTPUT_QUOTE_CHAR = 'csv_output_quote_char'
|
||||
# Filter for output column values
|
||||
CSV_OUTPUT_ROW_FILTER = 'csv_output_row_filter'
|
||||
# Mode (and|or) for output column values
|
||||
CSV_OUTPUT_ROW_FILTER_MODE = 'csv_output_row_filter_mode'
|
||||
# Filter for output column drop values
|
||||
CSV_OUTPUT_ROW_DROP_FILTER = 'csv_output_row_drop_filter'
|
||||
# Mode (and|or) for output column drop values
|
||||
CSV_OUTPUT_ROW_DROP_FILTER_MODE = 'csv_output_row_drop_filter_mode'
|
||||
# Limit number of output rows
|
||||
CSV_OUTPUT_ROW_LIMIT = 'csv_output_row_limit'
|
||||
# Output sort headers
|
||||
CSV_OUTPUT_SORT_HEADERS = 'csv_output_sort_headers'
|
||||
# Column header subfield name delimiter in CSV output file
|
||||
CSV_OUTPUT_SUBFIELD_DELIMITER = 'csv_output_subfield_delimiter'
|
||||
# Add timestamp column to CSV output file
|
||||
CSV_OUTPUT_TIMESTAMP_COLUMN = 'csv_output_timestamp_column'
|
||||
# Output rows for users even if they do not have the print object (delegate, filters, ...)
|
||||
CSV_OUTPUT_USERS_AUDIT = 'csv_output_users_audit'
|
||||
# custmerId from gam.cfg or retrieved from Google
|
||||
CUSTOMER_ID = 'customer_id'
|
||||
# If debug_level > 0: extra_args['prettyPrint'] = True, httplib2.debuglevel = gam_debug_level, appsObj.debug = True
|
||||
DEBUG_LEVEL = 'debug_level'
|
||||
# redact sensitive credentials from debug output
|
||||
DEBUG_REDACTION = 'debug_redaction'
|
||||
# Developer Preview APIs
|
||||
DEVELOPER_PREVIEW_APIS = 'developer_preview_apis'
|
||||
# Developer Preview API Key
|
||||
DEVELOPER_PREVIEW_API_KEY = 'developer_preview_api_key'
|
||||
# When retrieving lists of ChromeOS devices from API, how many should be retrieved in each chunk
|
||||
DEVICE_MAX_RESULTS = 'device_max_results'
|
||||
# Domain obtained from gam.cfg or oauth2.txt
|
||||
DOMAIN = 'domain'
|
||||
# directory for file output
|
||||
DRIVE_DIR = 'drive_dir'
|
||||
# When retrieving lists of Drive files/folders from API, how many should be retrieved in each chunk
|
||||
DRIVE_MAX_RESULTS = 'drive_max_results'
|
||||
# When processing email messages in batches, how many should be processed in each batch
|
||||
EMAIL_BATCH_SIZE = 'email_batch_size'
|
||||
# Enable Delegated Admin Service Account
|
||||
ENABLE_DASA = 'enable_dasa'
|
||||
# Enable Cloud Session Reauthentication by borrowing a RAPT token from gcloud command
|
||||
ENABLE_GCLOUD_REAUTH = 'enable_gcloud_reauth'
|
||||
# When retrieving lists of calendar events from API, how many should be retrieved in each chunk
|
||||
EVENT_MAX_RESULTS = 'event_max_results'
|
||||
# Path to extra_args.txt
|
||||
EXTRA_ARGS = 'extra_args'
|
||||
# Google Cloud Project Organization ID
|
||||
GCP_ORG_ID = 'gcp_org_id'
|
||||
# Gmail CSE certificates directory
|
||||
GMAIL_CSE_INCERT_DIR = 'gmail_cse_incert_dir'
|
||||
# Gmail CSE KACL wrapped key files
|
||||
GMAIL_CSE_INKEY_DIR = 'gmail_cse_inkey_dir'
|
||||
# directory for file input
|
||||
INPUT_DIR = 'input_dir'
|
||||
# When processing items in batches, how many seconds should GAM wait between batches
|
||||
INTER_BATCH_WAIT = 'inter_batch_wait'
|
||||
# When retrieving lists of licenses from API, how many should be retrieved in each chunk
|
||||
LICENSE_MAX_RESULTS = 'license_max_results'
|
||||
# License SKUs to process
|
||||
LICENSE_SKUS = 'license_skus'
|
||||
# When retrieving lists of Google Group members from API, how many should be retrieved in each chunk
|
||||
MEMBER_MAX_RESULTS = 'member_max_results'
|
||||
# CI API Group members max page size when view=BASIC
|
||||
MEMBER_MAX_RESULTS_CI_BASIC = 'member_max_results_ci_basic'
|
||||
# CI API Group members max page size when view=FULL
|
||||
MEMBER_MAX_RESULTS_CI_FULL = 'member_max_results_ci_full'
|
||||
# When deleting or modifying Gmail messages, how many should be processed in each batch
|
||||
MESSAGE_BATCH_SIZE = 'message_batch_size'
|
||||
# When retrieving lists of Gmail messages from API, how many should be retrieved in each chunk
|
||||
MESSAGE_MAX_RESULTS = 'message_max_results'
|
||||
# When retrieving lists of Mobile devices from API, how many should be retrieved in each chunk
|
||||
MOBILE_MAX_RESULTS = 'mobile_max_results'
|
||||
# Number of parallel multiprocess pool.apply_async calls; -1: no limit, 0: NUM_THREADS, >0: specific limit
|
||||
MULTIPROCESS_POOL_LIMIT = 'multiprocess_pool_limit'
|
||||
# Value to substitute for NEVER_TIME
|
||||
NEVER_TIME = 'never_time'
|
||||
# If no_browser is False, writeCSVfile won't open a browser when todrive is set
|
||||
# and doOAuthRequest prints a link and waits for the verification code when oauth2.txt is being created
|
||||
NO_BROWSER = 'no_browser'
|
||||
# Disable GAM API caching
|
||||
NO_CACHE = 'no_cache'
|
||||
# Do noit use URL shortner for authentication URLs
|
||||
NO_SHORT_URLS = 'no_short_urls'
|
||||
# Disable GAM update check
|
||||
NO_UPDATE_CHECK = 'no_update_check'
|
||||
# Disable SSL certificate validation
|
||||
NO_VERIFY_SSL = 'no_verify_ssl'
|
||||
# Number of threads for gam tbatch
|
||||
NUM_TBATCH_THREADS = 'num_tbatch_threads'
|
||||
# Number of threads for gam batch/csv
|
||||
NUM_THREADS = 'num_threads'
|
||||
# Path to oauth2.txt
|
||||
OAUTH2_TXT = 'oauth2_txt'
|
||||
# File permissions for oauth2.txt.lock
|
||||
OAUTH2_TXT_LOCK_MODE = 'oauth2_txt_lock_mode'
|
||||
# Path to oauth2service.json
|
||||
OAUTH2SERVICE_JSON = 'oauth2service_json'
|
||||
# Output date format, empty defalts to ISOFormat
|
||||
OUTPUT_DATEFORMAT = 'output_dateformat'
|
||||
# Output time format, empty defalts to ISOFormat
|
||||
OUTPUT_TIMEFORMAT = 'output_timeformat'
|
||||
# When retrieving lists of people from API, how many should be retrieved in each chunk
|
||||
PEOPLE_MAX_RESULTS = 'people_max_results'
|
||||
# Domains for print alises|groups|users
|
||||
PRINT_AGU_DOMAINS = 'print_agu_domains'
|
||||
# OrgUnits for print cros
|
||||
PRINT_CROS_OUS = 'print_cros_ous'
|
||||
# OrgUnits and children for print cros
|
||||
PRINT_CROS_OUS_AND_CHILDREN = 'print_cros_ous_and_children'
|
||||
# Number of seconds to wait for batch/csv processes to complete
|
||||
PROCESS_WAIT_LIMIT = 'process_wait_limit'
|
||||
# Use quick method to move Chromebooks to OU
|
||||
QUICK_CROS_MOVE = 'quick_cros_move'
|
||||
# Quick info user: nogroups nolicenses noschemas
|
||||
QUICK_INFO_USER = 'quick_info_user'
|
||||
# resellerId from gam.cfg or retrieved from Google
|
||||
RESELLER_ID = 'reseller_id'
|
||||
# Retry service not available errors on API calls
|
||||
RETRY_API_SERVICE_NOT_AVAILABLE = 'retry_api_service_not_available'
|
||||
# Default section to use for processing
|
||||
SECTION = 'section'
|
||||
# Show API calls retry data
|
||||
SHOW_API_CALLS_RETRY_DATA = 'show_api_calls_retry_data'
|
||||
# Show commands when processing batch/csv/loop
|
||||
SHOW_COMMANDS = 'show_commands'
|
||||
# Convert newlines in text fields to "\n" in show commands
|
||||
SHOW_CONVERT_CR_NL = 'show_convert_cr_nl'
|
||||
# Add (n/m) to end of messages if number of items to be processed exceeds this number
|
||||
SHOW_COUNTS_MIN = 'show_counts_min'
|
||||
# Enable/disable "Getting ... " messages
|
||||
SHOW_GETTINGS = 'show_gettings'
|
||||
# Enable/disable NL at end of "Got ..." messages
|
||||
SHOW_GETTINGS_GOT_NL = 'show_gettings_got_nl'
|
||||
# Enable/disable showing multiprocess info in redirected stdout/stderr
|
||||
SHOW_MULTIPROCESS_INFO = 'show_multiprocess_info'
|
||||
# SMTP fqdn
|
||||
SMTP_FQDN = 'smtp_fqdn'
|
||||
# SMTP host
|
||||
SMTP_HOST = 'smtp_host'
|
||||
# SMTP username
|
||||
SMTP_USERNAME = 'smtp_username'
|
||||
# SMTP password
|
||||
SMTP_PASSWORD = 'smtp_password'
|
||||
# Time Zone
|
||||
TIMEZONE = 'timezone'
|
||||
## Minimum TLS Version required for HTTPS connections
|
||||
TLS_MIN_VERSION = 'tls_min_version'
|
||||
## Maximum TLS Version used for HTTPS connections
|
||||
TLS_MAX_VERSION = 'tls_max_version'
|
||||
# Clear basic filter when updating an existing sheet
|
||||
TODRIVE_CLEARFILTER = 'todrive_clearfilter'
|
||||
# Use client access for todrive
|
||||
TODRIVE_CLIENTACCESS = 'todrive_clientaccess'
|
||||
# Enable conversion to Google Sheets when uploading todrive files
|
||||
TODRIVE_CONVERSION = 'todrive_conversion'
|
||||
# Save local copy of CSV file
|
||||
TODRIVE_LOCALCOPY = 'todrive_localcopy'
|
||||
# Specify locale for Google Sheets
|
||||
TODRIVE_LOCALE = 'todrive_locale'
|
||||
# Suppress opening browser on todrive upload
|
||||
TODRIVE_NOBROWSER = 'todrive_nobrowser'
|
||||
# Suppress sending email on todrive upload
|
||||
TODRIVE_NOEMAIL = 'todrive_noemail'
|
||||
# No escape character in CSV output file
|
||||
TODRIVE_NO_ESCAPE_CHAR = 'todrive_no_escape_char'
|
||||
# ID/Name of parent folder for todrive files
|
||||
TODRIVE_PARENT = 'todrive_parent'
|
||||
# Append timestamp to todrive sheet name
|
||||
TODRIVE_SHEET_TIMESTAMP = 'todrive_sheet_timestamp'
|
||||
# Sheet timestamp format, empty defalts to ISOFormat
|
||||
TODRIVE_SHEET_TIMEFORMAT = 'todrive_sheet_timeformat'
|
||||
# Append timestamp to todrive file name
|
||||
TODRIVE_TIMESTAMP = 'todrive_timestamp'
|
||||
# Timestamp format, empty defalts to ISOFormat
|
||||
TODRIVE_TIMEFORMAT = 'todrive_timeformat'
|
||||
# Specify timezone for Google Sheets
|
||||
TODRIVE_TIMEZONE = 'todrive_timezone'
|
||||
# Upload data files with no data
|
||||
TODRIVE_UPLOAD_NODATA = 'todrive_upload_nodata'
|
||||
# User for todrive files
|
||||
TODRIVE_USER = 'todrive_user'
|
||||
# Truncate Client ID
|
||||
TRUNCATE_CLIENT_ID = 'truncate_client_id'
|
||||
# Update CrOS org unit with orgUnitId
|
||||
UPDATE_CROS_OU_WITH_ID = 'update_cros_ou_with_id'
|
||||
# Use admin access for chat where possible
|
||||
USE_CHAT_ADMIN_ACCESS = 'use_chat_admin_access'
|
||||
# Use course owner for course access
|
||||
USE_COURSE_OWNER_ACCESS = 'use_course_owner_access'
|
||||
# Use Project ID as Project Name and App Name
|
||||
USE_PROJECTID_AS_NAME = 'use_projectid_as_name'
|
||||
# When retrieving lists of Users from API, how many should be retrieved in each chunk
|
||||
USER_MAX_RESULTS = 'user_max_results'
|
||||
# User service account access only, no client access
|
||||
USER_SERVICE_ACCOUNT_ACCESS_ONLY = 'user_service_account_access_only'
|
||||
|
||||
CSV_INPUT_ROW_FILTER_ITEMS = {CSV_INPUT_ROW_FILTER, CSV_INPUT_ROW_FILTER_MODE,
|
||||
CSV_INPUT_ROW_DROP_FILTER, CSV_INPUT_ROW_DROP_FILTER_MODE,
|
||||
CSV_INPUT_ROW_LIMIT}
|
||||
|
||||
CSV_OUTPUT_ROW_FILTER_ITEMS = {CSV_OUTPUT_HEADER_FILTER, CSV_OUTPUT_HEADER_DROP_FILTER,
|
||||
CSV_OUTPUT_HEADER_FORCE, CSV_OUTPUT_HEADER_ORDER,
|
||||
CSV_OUTPUT_HEADER_REQUIRED,
|
||||
CSV_OUTPUT_ROW_FILTER, CSV_OUTPUT_ROW_FILTER_MODE,
|
||||
CSV_OUTPUT_ROW_DROP_FILTER, CSV_OUTPUT_ROW_DROP_FILTER_MODE,
|
||||
CSV_OUTPUT_ROW_LIMIT}
|
||||
|
||||
Defaults = {
|
||||
ACTIVITY_MAX_RESULTS: '100',
|
||||
ADMIN_EMAIL: '',
|
||||
API_CALLS_RATE_CHECK: FALSE,
|
||||
API_CALLS_RATE_LIMIT: '100',
|
||||
API_CALLS_TRIES_LIMIT: '10',
|
||||
AUTO_BATCH_MIN: '0',
|
||||
BAIL_ON_INTERNAL_ERROR_TRIES: '2',
|
||||
BATCH_SIZE: '50',
|
||||
CACERTS_PEM: '',
|
||||
CACHE_DIR: '',
|
||||
CACHE_DISCOVERY_ONLY: TRUE,
|
||||
CHARSET: DEFAULT_CHARSET,
|
||||
CHANNEL_CUSTOMER_ID: '',
|
||||
CHAT_MAX_RESULTS: '100',
|
||||
CLASSROOM_MAX_RESULTS: '0',
|
||||
CLIENT_SECRETS_JSON: FN_CLIENT_SECRETS_JSON,
|
||||
CLOCK_SKEW_IN_SECONDS: '10',
|
||||
CMDLOG: '',
|
||||
CMDLOG_MAX_BACKUPS: 5,
|
||||
CMDLOG_MAX_KILO_BYTES: 1000,
|
||||
COMMANDDATA_CLIENTACCESS: FALSE,
|
||||
CONFIG_DIR: '',
|
||||
CONTACT_MAX_RESULTS: '100',
|
||||
CSV_INPUT_COLUMN_DELIMITER: ',',
|
||||
CSV_INPUT_NO_ESCAPE_CHAR: TRUE,
|
||||
CSV_INPUT_QUOTE_CHAR: '\'"\'',
|
||||
CSV_INPUT_ROW_FILTER: '',
|
||||
CSV_INPUT_ROW_FILTER_MODE: 'allmatch',
|
||||
CSV_INPUT_ROW_DROP_FILTER: '',
|
||||
CSV_INPUT_ROW_DROP_FILTER_MODE: 'anymatch',
|
||||
CSV_INPUT_ROW_LIMIT: '0',
|
||||
CSV_OUTPUT_COLUMN_DELIMITER: ',',
|
||||
CSV_OUTPUT_CONVERT_CR_NL: FALSE,
|
||||
CSV_OUTPUT_NO_ESCAPE_CHAR: FALSE,
|
||||
CSV_OUTPUT_FIELD_DELIMITER: "' '",
|
||||
CSV_OUTPUT_HEADER_FILTER: '',
|
||||
CSV_OUTPUT_HEADER_DROP_FILTER: '',
|
||||
CSV_OUTPUT_HEADER_FORCE: '',
|
||||
CSV_OUTPUT_HEADER_ORDER: '',
|
||||
CSV_OUTPUT_HEADER_REQUIRED: '',
|
||||
CSV_OUTPUT_LINE_TERMINATOR: 'lf',
|
||||
CSV_OUTPUT_QUOTE_CHAR: '\'"\'',
|
||||
CSV_OUTPUT_ROW_FILTER: '',
|
||||
CSV_OUTPUT_ROW_FILTER_MODE: 'allmatch',
|
||||
CSV_OUTPUT_ROW_DROP_FILTER: '',
|
||||
CSV_OUTPUT_ROW_DROP_FILTER_MODE: 'anymatch',
|
||||
CSV_OUTPUT_ROW_LIMIT: '0',
|
||||
CSV_OUTPUT_SORT_HEADERS: '',
|
||||
CSV_OUTPUT_SUBFIELD_DELIMITER: '.',
|
||||
CSV_OUTPUT_TIMESTAMP_COLUMN: '',
|
||||
CSV_OUTPUT_USERS_AUDIT: FALSE,
|
||||
CUSTOMER_ID: MY_CUSTOMER,
|
||||
DEBUG_LEVEL: '0',
|
||||
DEBUG_REDACTION: TRUE,
|
||||
DEVELOPER_PREVIEW_APIS: '',
|
||||
DEVELOPER_PREVIEW_API_KEY: '',
|
||||
DEVICE_MAX_RESULTS: '200',
|
||||
DOMAIN: '',
|
||||
DRIVE_DIR: '',
|
||||
DRIVE_MAX_RESULTS: '1000',
|
||||
EMAIL_BATCH_SIZE: '50',
|
||||
ENABLE_DASA: FALSE,
|
||||
ENABLE_GCLOUD_REAUTH: FALSE,
|
||||
EVENT_MAX_RESULTS: '250',
|
||||
EXTRA_ARGS: '',
|
||||
GCP_ORG_ID: '',
|
||||
GMAIL_CSE_INCERT_DIR: '',
|
||||
GMAIL_CSE_INKEY_DIR: '',
|
||||
INPUT_DIR: '.',
|
||||
INTER_BATCH_WAIT: '0',
|
||||
LICENSE_MAX_RESULTS: '100',
|
||||
LICENSE_SKUS: '',
|
||||
MEMBER_MAX_RESULTS: '200',
|
||||
MEMBER_MAX_RESULTS_CI_BASIC: '1000',
|
||||
MEMBER_MAX_RESULTS_CI_FULL: '500',
|
||||
MESSAGE_BATCH_SIZE: '50',
|
||||
MESSAGE_MAX_RESULTS: '500',
|
||||
MOBILE_MAX_RESULTS: '100',
|
||||
MULTIPROCESS_POOL_LIMIT: '0',
|
||||
NEVER_TIME: NEVER,
|
||||
NO_BROWSER: FALSE,
|
||||
NO_CACHE: FALSE,
|
||||
NO_SHORT_URLS: TRUE,
|
||||
NO_UPDATE_CHECK: TRUE,
|
||||
NO_VERIFY_SSL: FALSE,
|
||||
NUM_TBATCH_THREADS: '2',
|
||||
NUM_THREADS: '5',
|
||||
OAUTH2_TXT: FN_OAUTH2_TXT,
|
||||
OAUTH2_TXT_LOCK_MODE: '644',
|
||||
OAUTH2SERVICE_JSON: FN_OAUTH2SERVICE_JSON,
|
||||
OUTPUT_DATEFORMAT: '',
|
||||
OUTPUT_TIMEFORMAT: '',
|
||||
PEOPLE_MAX_RESULTS: '100',
|
||||
PRINT_AGU_DOMAINS: '',
|
||||
PRINT_CROS_OUS: '',
|
||||
PRINT_CROS_OUS_AND_CHILDREN: '',
|
||||
PROCESS_WAIT_LIMIT: '0',
|
||||
QUICK_CROS_MOVE: FALSE,
|
||||
QUICK_INFO_USER: FALSE,
|
||||
RESELLER_ID: '',
|
||||
RETRY_API_SERVICE_NOT_AVAILABLE: FALSE,
|
||||
SECTION: '',
|
||||
SHOW_API_CALLS_RETRY_DATA: FALSE,
|
||||
SHOW_COMMANDS: FALSE,
|
||||
SHOW_CONVERT_CR_NL: FALSE,
|
||||
SHOW_COUNTS_MIN: '1',
|
||||
SHOW_GETTINGS: TRUE,
|
||||
SHOW_GETTINGS_GOT_NL: FALSE,
|
||||
SHOW_MULTIPROCESS_INFO: FALSE,
|
||||
SMTP_FQDN: '',
|
||||
SMTP_HOST: '',
|
||||
SMTP_USERNAME: '',
|
||||
SMTP_PASSWORD: '',
|
||||
TIMEZONE: 'utc',
|
||||
TLS_MIN_VERSION: 'TLSv1_3',
|
||||
TLS_MAX_VERSION: '',
|
||||
TODRIVE_CLEARFILTER: FALSE,
|
||||
TODRIVE_CLIENTACCESS: FALSE,
|
||||
TODRIVE_CONVERSION: TRUE,
|
||||
TODRIVE_LOCALCOPY: FALSE,
|
||||
TODRIVE_LOCALE: '',
|
||||
TODRIVE_NOBROWSER: '',
|
||||
TODRIVE_NOEMAIL: '',
|
||||
TODRIVE_NO_ESCAPE_CHAR: TRUE,
|
||||
TODRIVE_PARENT: 'root',
|
||||
TODRIVE_SHEET_TIMESTAMP: 'copy', # copy from TODRIVE_TIMESTAMP
|
||||
TODRIVE_SHEET_TIMEFORMAT: 'copy', # copy from TODRIVE_TIMEFORMAT
|
||||
TODRIVE_TIMESTAMP: FALSE,
|
||||
TODRIVE_TIMEFORMAT: '',
|
||||
TODRIVE_TIMEZONE: '',
|
||||
TODRIVE_UPLOAD_NODATA: TRUE,
|
||||
TODRIVE_USER: '',
|
||||
TRUNCATE_CLIENT_ID: FALSE,
|
||||
UPDATE_CROS_OU_WITH_ID: FALSE,
|
||||
USE_CHAT_ADMIN_ACCESS: FALSE,
|
||||
USE_COURSE_OWNER_ACCESS: FALSE,
|
||||
USE_PROJECTID_AS_NAME: FALSE,
|
||||
USER_MAX_RESULTS: '500',
|
||||
USER_SERVICE_ACCOUNT_ACCESS_ONLY: FALSE,
|
||||
}
|
||||
|
||||
Values = {DEBUG_LEVEL: 0}
|
||||
|
||||
TYPE_BOOLEAN = 'bool'
|
||||
TYPE_CHARACTER = 'char'
|
||||
TYPE_CHOICE = 'choi'
|
||||
TYPE_CHOICE_LIST = 'chol'
|
||||
TYPE_DATETIME = 'datm'
|
||||
TYPE_DIRECTORY = 'dire'
|
||||
TYPE_EMAIL = 'emai'
|
||||
TYPE_EMAIL_OPTIONAL = 'emao'
|
||||
TYPE_FILE = 'file'
|
||||
TYPE_FLOAT = 'floa'
|
||||
TYPE_HEADERFILTER = 'heaf'
|
||||
TYPE_HEADERFORCEREQUIRED = 'hefr'
|
||||
TYPE_HEADERORDER = 'heor'
|
||||
TYPE_INTEGER = 'inte'
|
||||
TYPE_LANGUAGE = 'lang'
|
||||
TYPE_LOCALE = 'locl'
|
||||
TYPE_PASSWORD = 'pass'
|
||||
TYPE_ROWFILTER = 'rowf'
|
||||
TYPE_STRING = 'stri'
|
||||
TYPE_STRINGLIST = 'strl'
|
||||
TYPE_TIMEZONE = 'tmzn'
|
||||
|
||||
VAR_TYPE = 'type'
|
||||
VAR_ENVVAR = 'enva'
|
||||
VAR_CHOICES = 'chod'
|
||||
VAR_LIMITS = 'lmit'
|
||||
VAR_SFFT = 'sfft'
|
||||
VAR_SIGFILE = 'sigf'
|
||||
VAR_ACCESS = 'aces'
|
||||
|
||||
VAR_INFO = {
|
||||
ACTIVITY_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 500)},
|
||||
ADMIN_EMAIL: {VAR_TYPE: TYPE_STRING, VAR_ENVVAR: 'GA_ADMIN_EMAIL', VAR_LIMITS: (0, None)},
|
||||
API_CALLS_RATE_CHECK: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
API_CALLS_RATE_LIMIT: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (50, None)},
|
||||
API_CALLS_TRIES_LIMIT: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (3, 30)},
|
||||
AUTO_BATCH_MIN: {VAR_TYPE: TYPE_INTEGER, VAR_ENVVAR: 'GAM_AUTOBATCH', VAR_LIMITS: (0, 100)},
|
||||
BAIL_ON_INTERNAL_ERROR_TRIES: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 10)},
|
||||
BATCH_SIZE: {VAR_TYPE: TYPE_INTEGER, VAR_ENVVAR: 'GAM_BATCH_SIZE', VAR_LIMITS: (1, 1000)},
|
||||
CACERTS_PEM: {VAR_TYPE: TYPE_FILE, VAR_ENVVAR: 'GAM_CA_FILE', VAR_ACCESS: os.R_OK},
|
||||
CACHE_DIR: {VAR_TYPE: TYPE_DIRECTORY, VAR_ENVVAR: 'GAMCACHEDIR'},
|
||||
CACHE_DISCOVERY_ONLY: {VAR_TYPE: TYPE_BOOLEAN, VAR_SIGFILE: 'allcache.txt', VAR_SFFT: (TRUE, FALSE)},
|
||||
CHARSET: {VAR_TYPE: TYPE_STRING, VAR_ENVVAR: 'GAM_CHARSET', VAR_LIMITS: (1, None)},
|
||||
CHANNEL_CUSTOMER_ID: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
CHAT_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 1000)},
|
||||
CLASSROOM_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (0, 1000)},
|
||||
CLIENT_SECRETS_JSON: {VAR_TYPE: TYPE_FILE, VAR_ENVVAR: 'CLIENTSECRETS', VAR_ACCESS: os.R_OK},
|
||||
CLOCK_SKEW_IN_SECONDS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (10, 3600)},
|
||||
CMDLOG: {VAR_TYPE: TYPE_FILE, VAR_ACCESS: os.W_OK},
|
||||
CMDLOG_MAX_BACKUPS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 10)},
|
||||
CMDLOG_MAX_KILO_BYTES: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (100, 10000)},
|
||||
COMMANDDATA_CLIENTACCESS: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
CONFIG_DIR: {VAR_TYPE: TYPE_DIRECTORY, VAR_ENVVAR: 'GAMUSERCONFIGDIR'},
|
||||
CONTACT_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 10000)},
|
||||
CSV_INPUT_COLUMN_DELIMITER: {VAR_TYPE: TYPE_CHARACTER},
|
||||
CSV_INPUT_NO_ESCAPE_CHAR: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
CSV_INPUT_QUOTE_CHAR: {VAR_TYPE: TYPE_CHARACTER},
|
||||
CSV_INPUT_ROW_FILTER: {VAR_TYPE: TYPE_ROWFILTER},
|
||||
CSV_INPUT_ROW_FILTER_MODE: {VAR_TYPE: TYPE_CHOICE, VAR_CHOICES: {'allmatch': True, 'anymatch': False}},
|
||||
CSV_INPUT_ROW_DROP_FILTER: {VAR_TYPE: TYPE_ROWFILTER},
|
||||
CSV_INPUT_ROW_DROP_FILTER_MODE: {VAR_TYPE: TYPE_CHOICE, VAR_CHOICES: {'allmatch': True, 'anymatch': False}},
|
||||
CSV_INPUT_ROW_LIMIT: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (0, None)},
|
||||
CSV_OUTPUT_COLUMN_DELIMITER: {VAR_TYPE: TYPE_CHARACTER},
|
||||
CSV_OUTPUT_CONVERT_CR_NL: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
CSV_OUTPUT_NO_ESCAPE_CHAR: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
CSV_OUTPUT_FIELD_DELIMITER: {VAR_TYPE: TYPE_CHARACTER},
|
||||
CSV_OUTPUT_HEADER_FILTER: {VAR_TYPE: TYPE_HEADERFILTER},
|
||||
CSV_OUTPUT_HEADER_DROP_FILTER: {VAR_TYPE: TYPE_HEADERFILTER},
|
||||
CSV_OUTPUT_HEADER_FORCE: {VAR_TYPE: TYPE_HEADERFORCEREQUIRED},
|
||||
CSV_OUTPUT_HEADER_ORDER: {VAR_TYPE: TYPE_HEADERORDER},
|
||||
CSV_OUTPUT_HEADER_REQUIRED: {VAR_TYPE: TYPE_HEADERFORCEREQUIRED},
|
||||
CSV_OUTPUT_LINE_TERMINATOR: {VAR_TYPE: TYPE_CHOICE, VAR_CHOICES: {'cr': '\r', 'lf': '\n', 'crlf': '\r\n'}},
|
||||
CSV_OUTPUT_QUOTE_CHAR: {VAR_TYPE: TYPE_CHARACTER},
|
||||
CSV_OUTPUT_ROW_FILTER: {VAR_TYPE: TYPE_ROWFILTER},
|
||||
CSV_OUTPUT_ROW_FILTER_MODE: {VAR_TYPE: TYPE_CHOICE, VAR_CHOICES: {'allmatch': True, 'anymatch': False}},
|
||||
CSV_OUTPUT_ROW_DROP_FILTER: {VAR_TYPE: TYPE_ROWFILTER},
|
||||
CSV_OUTPUT_ROW_DROP_FILTER_MODE: {VAR_TYPE: TYPE_CHOICE, VAR_CHOICES: {'allmatch': True, 'anymatch': False}},
|
||||
CSV_OUTPUT_ROW_LIMIT: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (0, None)},
|
||||
CSV_OUTPUT_SORT_HEADERS: {VAR_TYPE: TYPE_STRINGLIST},
|
||||
CSV_OUTPUT_SUBFIELD_DELIMITER: {VAR_TYPE: TYPE_CHARACTER},
|
||||
CSV_OUTPUT_TIMESTAMP_COLUMN: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
CSV_OUTPUT_USERS_AUDIT: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
CUSTOMER_ID: {VAR_TYPE: TYPE_STRING, VAR_ENVVAR: 'CUSTOMER_ID', VAR_LIMITS: (0, None)},
|
||||
DEBUG_LEVEL: {VAR_TYPE: TYPE_INTEGER, VAR_SIGFILE: 'debug.gam', VAR_LIMITS: (0, None), VAR_SFFT: ('0', '4')},
|
||||
DEBUG_REDACTION: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
DEVELOPER_PREVIEW_APIS: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
DEVELOPER_PREVIEW_API_KEY: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
DEVICE_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 200)},
|
||||
DOMAIN: {VAR_TYPE: TYPE_STRING, VAR_ENVVAR: 'GA_DOMAIN', VAR_LIMITS: (0, None)},
|
||||
DRIVE_DIR: {VAR_TYPE: TYPE_DIRECTORY, VAR_ENVVAR: 'GAMDRIVEDIR'},
|
||||
DRIVE_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 1000)},
|
||||
EMAIL_BATCH_SIZE: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 100)},
|
||||
ENABLE_DASA: {VAR_TYPE: TYPE_BOOLEAN, VAR_SIGFILE: 'enabledasa.txt', VAR_SFFT: (FALSE, TRUE)},
|
||||
ENABLE_GCLOUD_REAUTH: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
EVENT_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 2500)},
|
||||
EXTRA_ARGS: {VAR_TYPE: TYPE_FILE, VAR_SIGFILE: FN_EXTRA_ARGS_TXT, VAR_SFFT: ('', FN_EXTRA_ARGS_TXT), VAR_ACCESS: os.R_OK},
|
||||
GCP_ORG_ID: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
GMAIL_CSE_INCERT_DIR: {VAR_TYPE: TYPE_DIRECTORY},
|
||||
GMAIL_CSE_INKEY_DIR: {VAR_TYPE: TYPE_DIRECTORY},
|
||||
INPUT_DIR: {VAR_TYPE: TYPE_DIRECTORY},
|
||||
INTER_BATCH_WAIT: {VAR_TYPE: TYPE_FLOAT, VAR_LIMITS: (0.0, 60.0)},
|
||||
LICENSE_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (10, 1000)},
|
||||
LICENSE_SKUS: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
MEMBER_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 200)},
|
||||
MEMBER_MAX_RESULTS_CI_BASIC: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 1000)},
|
||||
MEMBER_MAX_RESULTS_CI_FULL: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 500)},
|
||||
MESSAGE_BATCH_SIZE: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 1000)},
|
||||
MESSAGE_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 10000)},
|
||||
MOBILE_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 100)},
|
||||
MULTIPROCESS_POOL_LIMIT: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (-1, None)},
|
||||
NEVER_TIME: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
NO_BROWSER: {VAR_TYPE: TYPE_BOOLEAN, VAR_SIGFILE: 'nobrowser.txt', VAR_SFFT: (FALSE, TRUE)},
|
||||
NO_CACHE: {VAR_TYPE: TYPE_BOOLEAN, VAR_SIGFILE: 'nocache.txt', VAR_SFFT: (FALSE, TRUE)},
|
||||
NO_SHORT_URLS: {VAR_TYPE: TYPE_BOOLEAN, VAR_SIGFILE: 'noshorturls.txt', VAR_SFFT: (FALSE, TRUE)},
|
||||
NO_UPDATE_CHECK: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
NO_VERIFY_SSL: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
NUM_TBATCH_THREADS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 1000)},
|
||||
NUM_THREADS: {VAR_TYPE: TYPE_INTEGER, VAR_ENVVAR: 'GAM_THREADS', VAR_LIMITS: (1, 1000)},
|
||||
OAUTH2_TXT: {VAR_TYPE: TYPE_FILE, VAR_ENVVAR: 'OAUTHFILE', VAR_ACCESS: os.R_OK | os.W_OK},
|
||||
OAUTH2_TXT_LOCK_MODE: {VAR_TYPE: TYPE_CHOICE, VAR_CHOICES: {'644': 0o644, '664': 0o664, '666': 0o666}},
|
||||
OAUTH2SERVICE_JSON: {VAR_TYPE: TYPE_FILE, VAR_ENVVAR: 'OAUTHSERVICEFILE', VAR_ACCESS: os.R_OK | os.W_OK},
|
||||
OUTPUT_DATEFORMAT: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
OUTPUT_TIMEFORMAT: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
PEOPLE_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (0, 1000)},
|
||||
PRINT_AGU_DOMAINS: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
PRINT_CROS_OUS: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
PRINT_CROS_OUS_AND_CHILDREN: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
PROCESS_WAIT_LIMIT: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (0, None)},
|
||||
QUICK_CROS_MOVE: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
QUICK_INFO_USER: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
RESELLER_ID: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
RETRY_API_SERVICE_NOT_AVAILABLE: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
SECTION: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
SHOW_API_CALLS_RETRY_DATA: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
SHOW_COMMANDS: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
SHOW_CONVERT_CR_NL: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
SHOW_COUNTS_MIN: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (0, 100)},
|
||||
SHOW_GETTINGS: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
SHOW_GETTINGS_GOT_NL: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
SHOW_MULTIPROCESS_INFO: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
SMTP_FQDN: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
SMTP_HOST: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
SMTP_USERNAME: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
SMTP_PASSWORD: {VAR_TYPE: TYPE_PASSWORD, VAR_LIMITS: (0, None)},
|
||||
TIMEZONE: {VAR_TYPE: TYPE_TIMEZONE},
|
||||
TLS_MIN_VERSION: {VAR_TYPE: TYPE_CHOICE, VAR_ENVVAR: 'GAM_TLS_MIN_VERSION', VAR_CHOICES: TLS_CHOICE_MAP},
|
||||
TLS_MAX_VERSION: {VAR_TYPE: TYPE_CHOICE, VAR_ENVVAR: 'GAM_TLS_MAX_VERSION', VAR_CHOICES: TLS_CHOICE_MAP},
|
||||
TODRIVE_CLEARFILTER: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
TODRIVE_CLIENTACCESS: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
TODRIVE_CONVERSION: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
TODRIVE_LOCALCOPY: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
TODRIVE_LOCALE: {VAR_TYPE: TYPE_LOCALE},
|
||||
TODRIVE_NOBROWSER: {VAR_TYPE: TYPE_BOOLEAN, VAR_SIGFILE: 'nobrowser.txt', VAR_SFFT: (FALSE, TRUE)},
|
||||
TODRIVE_NOEMAIL: {VAR_TYPE: TYPE_BOOLEAN, VAR_SIGFILE: 'notdemail.txt', VAR_SFFT: (FALSE, TRUE)},
|
||||
TODRIVE_NO_ESCAPE_CHAR: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
TODRIVE_PARENT: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
TODRIVE_SHEET_TIMESTAMP: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
TODRIVE_SHEET_TIMEFORMAT: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
TODRIVE_TIMESTAMP: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
TODRIVE_TIMEFORMAT: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
TODRIVE_TIMEZONE: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
TODRIVE_UPLOAD_NODATA: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
TODRIVE_USER: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
TRUNCATE_CLIENT_ID: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
UPDATE_CROS_OU_WITH_ID: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
USE_CHAT_ADMIN_ACCESS: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
USE_COURSE_OWNER_ACCESS: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
USE_PROJECTID_AS_NAME: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
USER_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 500)},
|
||||
USER_SERVICE_ACCOUNT_ACCESS_ONLY: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,869 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2026 Ross Scroggs All Rights Reserved.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""GAM entity processing
|
||||
|
||||
"""
|
||||
|
||||
class GamEntity():
|
||||
|
||||
ROLE_MANAGER = 'MANAGER'
|
||||
ROLE_MEMBER = 'MEMBER'
|
||||
ROLE_OWNER = 'OWNER'
|
||||
ROLE_LIST = [ROLE_MANAGER, ROLE_MEMBER, ROLE_OWNER]
|
||||
ROLE_USER = 'USER'
|
||||
ROLE_MANAGER_MEMBER = ','.join([ROLE_MANAGER, ROLE_MEMBER])
|
||||
ROLE_MANAGER_OWNER = ','.join([ROLE_MANAGER, ROLE_OWNER])
|
||||
ROLE_MEMBER_OWNER = ','.join([ROLE_MEMBER, ROLE_OWNER])
|
||||
ROLE_MANAGER_MEMBER_OWNER = ','.join(ROLE_LIST)
|
||||
ROLE_PUBLIC = 'PUBLIC'
|
||||
ROLE_ALL = ROLE_MANAGER_MEMBER_OWNER
|
||||
|
||||
TYPE_CBCM_BROWSER = 'CBCM_BROWSER'
|
||||
TYPE_CUSTOMER = 'CUSTOMER'
|
||||
TYPE_EXTERNAL = 'EXTERNAL'
|
||||
TYPE_OTHER = 'OTHER'
|
||||
TYPE_GROUP = 'GROUP'
|
||||
TYPE_SERVICE_ACCOUNT = 'SERVICE_ACCOUNT'
|
||||
TYPE_USER = 'USER'
|
||||
|
||||
# Keys into NAMES; arbitrary values but must be unique
|
||||
ACCESS_TOKEN = 'atok'
|
||||
ACCOUNT = 'acct'
|
||||
ACTION = 'actn'
|
||||
ACTIVITY = 'actv'
|
||||
ADMINISTRATOR = 'admi'
|
||||
ADMIN_ROLE = 'adro'
|
||||
ADMIN_ROLE_ASSIGNMENT = 'adra'
|
||||
ALERT = 'alrt'
|
||||
ALERT_ID = 'alri'
|
||||
ALERT_FEEDBACK = 'alfb'
|
||||
ALERT_FEEDBACK_ID = 'alfi'
|
||||
ALERT_SETTINGS = 'alrs'
|
||||
ALIAS = 'alia'
|
||||
ALIAS_EMAIL = 'alie'
|
||||
ALIAS_TARGET = 'alit'
|
||||
ANALYTIC_ACCOUNT = 'anac'
|
||||
ANALYTIC_ACCOUNT_SUMMARY = 'anas'
|
||||
ANALYTIC_DATASTREAM = 'anad'
|
||||
ANALYTIC_PROPERTY = 'anap'
|
||||
API = 'api '
|
||||
APP_ACCESS_SETTINGS = 'apps'
|
||||
APP_ID = 'appi'
|
||||
APP_NAME = 'appn'
|
||||
APPLICATION_SPECIFIC_PASSWORD = 'aspa'
|
||||
ARROWS_ENABLED = 'arro'
|
||||
ATTACHMENT = 'atta'
|
||||
ATTENDEE = 'atnd'
|
||||
AUDIT_ACTIVITY_REQUEST = 'auda'
|
||||
AUDIT_EXPORT_REQUEST = 'audx'
|
||||
AUDIT_MONITOR_REQUEST = 'audm'
|
||||
BACKUP_VERIFICATION_CODES = 'buvc'
|
||||
BUILDING = 'bldg'
|
||||
BUILDING_ID = 'bldi'
|
||||
BUSINESS_PROFILE_ACCOUNT = 'bpac'
|
||||
CAA_LEVEL = 'calv'
|
||||
CALENDAR = 'cale'
|
||||
CALENDAR_ACL = 'cacl'
|
||||
CALENDAR_SETTINGS = 'cset'
|
||||
CHANNEL_CUSTOMER = 'chcu'
|
||||
CHANNEL_CUSTOMER_ENTITLEMENT = 'chce'
|
||||
CHANNEL_OFFER = 'chof'
|
||||
CHANNEL_PRODUCT = 'chpr'
|
||||
CHANNEL_SKU = 'chsk'
|
||||
CHAT_BOT = 'chbo'
|
||||
CHAT_ADMIN = 'chad'
|
||||
CHAT_EMOJI = 'chem'
|
||||
CHAT_EVENT = 'chev'
|
||||
CHAT_MANAGER_USER = 'chgu'
|
||||
CHAT_MEMBER = 'chme'
|
||||
CHAT_MEMBER_GROUP = 'chmg'
|
||||
CHAT_MEMBER_USER = 'chmu'
|
||||
CHAT_MESSAGE = 'chms'
|
||||
CHAT_MESSAGE_ID = 'chmi'
|
||||
CHAT_OWNER_USER = 'chou'
|
||||
CHAT_SECTION = 'chse'
|
||||
CHAT_SECTION_ITEM = 'chsi'
|
||||
CHAT_SPACE = 'chsp'
|
||||
CHAT_THREAD = 'chth'
|
||||
CHILD_ORGANIZATIONAL_UNIT = 'corg'
|
||||
CHROME_APP = 'capp'
|
||||
CHROME_APP_DEVICE = 'capd'
|
||||
CHROME_BROWSER = 'chbr'
|
||||
CHROME_BROWSER_ENROLLMENT_TOKEN = 'cbet'
|
||||
CHROME_CHANNEL = 'chan'
|
||||
CHROME_DEVICE = 'chdv'
|
||||
CHROME_DEVICE_COUNT = 'chdc'
|
||||
CHROME_MODEL = 'chmo'
|
||||
CHROME_NETWORK_ID = 'chni'
|
||||
CHROME_NETWORK_NAME = 'chnn'
|
||||
CHROME_PLATFORM = 'cpla'
|
||||
CHROME_POLICY = 'cpol'
|
||||
CHROME_POLICY_IMAGE = 'cpim'
|
||||
CHROME_POLICY_SCHEMA = 'cpsc'
|
||||
CHROME_PROFILE = 'cpro'
|
||||
CHROME_PROFILE_COMMAND = 'cpcm'
|
||||
CHROME_RELEASE = 'crel'
|
||||
CHROME_VERSION = 'cver'
|
||||
CLASSIFICATION_LABEL = 'dlab'
|
||||
CLASSIFICATION_LABEL_FIELD_ID = 'dlfi'
|
||||
CLASSIFICATION_LABEL_ID = 'dlid'
|
||||
CLASSIFICATION_LABEL_NAME = 'dlna'
|
||||
CLASSIFICATION_LABEL_PERMISSION = 'dlpe'
|
||||
CLASSIFICATION_LABEL_PERMISSION_NAME = 'dlpn'
|
||||
CLASSROOM_INVITATION = 'clai'
|
||||
CLASSROOM_INVITATION_OWNER = 'clio'
|
||||
CLASSROOM_INVITATION_STUDENT = 'clis'
|
||||
CLASSROOM_INVITATION_TEACHER = 'clit'
|
||||
CLASSROOM_OAUTH2_TXT_FILE = 'coa'
|
||||
CLASSROOM_USER_PROFILE = 'clup'
|
||||
CLIENT_ID = 'clid'
|
||||
CLIENT_SECRETS_JSON_FILE = 'csjf'
|
||||
CLOUD_IDENTITY_GROUP = 'cidg'
|
||||
CLOUD_STORAGE_BUCKET = 'clsb'
|
||||
CLOUD_STORAGE_FILE = 'clsf'
|
||||
COLLABORATOR = 'cola'
|
||||
COMMAND_ID = 'cmdi'
|
||||
COMPANY_DEVICE = 'codv'
|
||||
CONFIG_FILE = 'conf'
|
||||
CONTACT = 'cont'
|
||||
CONTACT_DELEGATE = 'cond'
|
||||
CONTACT_GROUP = 'cogr'
|
||||
CONTACT_GROUP_NAME = 'cogn'
|
||||
COPYFROM_COURSE = 'cfco'
|
||||
COPYFROM_GROUP = 'cfgr'
|
||||
COURSE = 'cour'
|
||||
COURSE_ALIAS = 'coal'
|
||||
COURSE_ANNOUNCEMENT = 'cann'
|
||||
COURSE_ANNOUNCEMENT_ID = 'caid'
|
||||
COURSE_ANNOUNCEMENT_STATE = 'cast'
|
||||
COURSE_MATERIAL_DRIVEFILE = 'comd'
|
||||
COURSE_MATERIAL_FORM = 'comf'
|
||||
COURSE_MATERIAL = 'cmtl'
|
||||
COURSE_MATERIAL_ID = 'cmid'
|
||||
COURSE_MATERIAL_STATE = 'cmst'
|
||||
COURSE_NAME = 'cona'
|
||||
COURSE_STATE = 'cost'
|
||||
COURSE_STUDENTGROUP = 'cosg'
|
||||
COURSE_STUDENTGROUP_MEMBER = 'csgm'
|
||||
COURSE_SUBMISSION_ID = 'csid'
|
||||
COURSE_SUBMISSION_STATE = 'csst'
|
||||
COURSE_TOPIC = 'ctop'
|
||||
COURSE_TOPIC_ID = 'ctoi'
|
||||
COURSE_WORK = 'cwrk'
|
||||
COURSE_WORK_ID = 'cwid'
|
||||
COURSE_WORK_STATE = 'cwst'
|
||||
CREATOR_ID = 'crid'
|
||||
CREDENTIALS = 'cred'
|
||||
CRITERIA = 'crit'
|
||||
CROS_DEVICE = 'cros'
|
||||
CROS_SERIAL_NUMBER = 'crsn'
|
||||
CSE_IDENTITY = 'csei'
|
||||
CSE_KEYPAIR = 'csek'
|
||||
CUSTOMER_DOMAIN = 'cudo'
|
||||
CUSTOMER_ID = 'cuid'
|
||||
DATE = 'date'
|
||||
DEFAULT_LANGUAGE = 'dfla'
|
||||
DELEGATE = 'dele'
|
||||
DELETED_USER = 'delu'
|
||||
DELIVERY = 'deli'
|
||||
DEVICE = 'devi'
|
||||
DEVICE_FILE = 'devf'
|
||||
DIRECTORY = 'drct'
|
||||
DEVICE_USER = 'devu'
|
||||
DEVICE_USER_CLIENT_STATE = 'ducs'
|
||||
DISCOVERY_JSON_FILE = 'disc'
|
||||
DOCUMENT = 'docu'
|
||||
DOMAIN = 'doma'
|
||||
DOMAIN_ALIAS = 'doal'
|
||||
DOMAIN_CONTACT = 'doco'
|
||||
DOMAIN_PEOPLE_CONTACT = 'dopc'
|
||||
DOMAIN_PROFILE = 'dopr'
|
||||
DRIVE_DISK_USAGE = 'drdu'
|
||||
DRIVE_FILE = 'dfil'
|
||||
DRIVE_FILE_COMMENT = 'filc'
|
||||
DRIVE_FILE_ID = 'fili'
|
||||
DRIVE_FILE_NAME = 'filn'
|
||||
DRIVE_FILE_RENAMED = 'firn'
|
||||
DRIVE_FILE_REVISION = 'filr'
|
||||
DRIVE_FILE_SHORTCUT = 'fils'
|
||||
DRIVE_FILE_OR_FOLDER = 'fifo'
|
||||
DRIVE_FILE_OR_FOLDER_ACL = 'fiac'
|
||||
DRIVE_FILE_OR_FOLDER_ID = 'fifi'
|
||||
DRIVE_FOLDER = 'fold'
|
||||
DRIVE_FOLDER_ID = 'foli'
|
||||
DRIVE_FOLDER_NAME = 'foln'
|
||||
DRIVE_FOLDER_PATH = 'folp'
|
||||
DRIVE_FOLDER_RENAMED = 'forn'
|
||||
DRIVE_FOLDER_SHORTCUT = 'fols'
|
||||
DRIVE_ORPHAN_FILE_OR_FOLDER = 'orph'
|
||||
DRIVE_PARENT_FOLDER = 'fipf'
|
||||
DRIVE_PARENT_FOLDER_ID = 'fipi'
|
||||
DRIVE_PARENT_FOLDER_REFERENCE = 'pfrf'
|
||||
DRIVE_PATH = 'drvp'
|
||||
DRIVE_SETTINGS = 'drvs'
|
||||
DRIVE_SHORTCUT = 'drsc'
|
||||
DRIVE_SHORTCUT_ID = 'dsci'
|
||||
DRIVE_3PSHORTCUT = 'dr3s'
|
||||
DRIVE_TRASH = 'drvt'
|
||||
EMAIL = 'emai'
|
||||
EMAIL_ALIAS = 'emal'
|
||||
EMAIL_SETTINGS = 'emse'
|
||||
END_TIME = 'endt'
|
||||
ENTITY = 'enti'
|
||||
EVENT = 'evnt'
|
||||
EVENT_BIRTHDAY = 'evbd'
|
||||
EVENT_FOCUSTIME = 'evft'
|
||||
EVENT_OUTOFOFFICE = 'evoo'
|
||||
EVENT_WORKINGLOCATION = 'evwl'
|
||||
FEATURE = 'feat'
|
||||
FIELD = 'fiel'
|
||||
FILE = 'file'
|
||||
FILE_PARENT_TREE = 'fptr'
|
||||
FILTER = 'filt'
|
||||
FORM = 'form'
|
||||
FORM_RESPONSE = 'frmr'
|
||||
FORWARD_ENABLED = 'fwde'
|
||||
FORWARDING_ADDRESS = 'fwda'
|
||||
GCP_FOLDER = 'gcpf'
|
||||
GCP_FOLDER_NAME = 'gcpn'
|
||||
GCP_ORG_ID = 'gcpo'
|
||||
GMAIL_PROFILE = 'gmpr'
|
||||
GROUP = 'grou'
|
||||
GROUP_ALIAS = 'gali'
|
||||
GROUP_EMAIL = 'gale'
|
||||
GROUP_MEMBERSHIP = 'gmem'
|
||||
GROUP_MEMBERSHIP_TREE = 'gmtr'
|
||||
GROUP_SETTINGS = 'gset'
|
||||
GROUP_TREE = 'gtre'
|
||||
GUARDIAN = 'guar'
|
||||
GUARDIAN_INVITATION = 'gari'
|
||||
GUARDIAN_AND_INVITATION = 'gani'
|
||||
GUEST_USER = 'gstu'
|
||||
IAM_POLICY = 'iamp'
|
||||
IMAP_ENABLED = 'imap'
|
||||
INBOUND_SSO_ASSIGNMENT = 'insa'
|
||||
INBOUND_SSO_CREDENTIALS = 'insc'
|
||||
INBOUND_SSO_PROFILE = 'insp'
|
||||
INSTANCE = 'inst'
|
||||
ITEM = 'item'
|
||||
ISSUER_CN = 'iscn'
|
||||
KEYBOARD_SHORTCUTS_ENABLED = 'kbsc'
|
||||
LABEL = 'labe'
|
||||
LABEL_ID = 'labi'
|
||||
LANGUAGE = 'lang'
|
||||
LICENSE = 'lice'
|
||||
LOCATION = 'loca'
|
||||
LOOKERSTUDIO_ASSET = 'lsas'
|
||||
LOOKERSTUDIO_ASSET_DATASOURCE = 'lsad'
|
||||
LOOKERSTUDIO_ASSETID = 'lsai'
|
||||
LOOKERSTUDIO_ASSET_REPORT = 'lsar'
|
||||
LOOKERSTUDIO_PERMISSION = 'lspe'
|
||||
MD5HASH = 'md5h'
|
||||
MEET_SPACE = 'mesp'
|
||||
MEET_CONFERENCE = 'msco'
|
||||
MEET_PARTICIPANT = 'msps'
|
||||
MEET_RECORDING = 'msre'
|
||||
MEET_TRANSCRIPT = 'mstr'
|
||||
MEMBER = 'memb'
|
||||
MEMBER_NOT_ARCHIVED = 'mena'
|
||||
MEMBER_ARCHIVED = 'mear'
|
||||
MEMBER_NOT_SUSPENDED = 'mens'
|
||||
MEMBER_SUSPENDED = 'mesu'
|
||||
MEMBER_NOT_SUSPENDED_NOT_ARCHIVED = 'nsna'
|
||||
MEMBER_SUSPENDED_ARCHIVED = 'suar'
|
||||
MEMBER_RESTRICTION = 'memr'
|
||||
MEMBER_URI = 'memu'
|
||||
MEMBERSHIP_TREE = 'metr'
|
||||
MESSAGE = 'mesg'
|
||||
MIMETYPE = 'mime'
|
||||
MOBILE_DEVICE = 'mobi'
|
||||
NAME = 'name'
|
||||
NONEDITABLE_ALIAS = 'neal'
|
||||
NOTE = 'note'
|
||||
NOTE_ACL = 'nota'
|
||||
NOTES_ACLS = 'naac'
|
||||
NOTIFICATION = 'noti'
|
||||
OAUTH2_TXT_FILE = 'oaut'
|
||||
OAUTH2SERVICE_JSON_FILE = 'oau2'
|
||||
ORGANIZATIONAL_UNIT = 'orgu'
|
||||
OTHER_CONTACT = 'otco'
|
||||
OWNER = 'ownr'
|
||||
OWNER_ID = 'owid'
|
||||
PAGE_SIZE = 'page'
|
||||
PARENT_ORGANIZATIONAL_UNIT = 'porg'
|
||||
PARTICIPANT = 'part'
|
||||
PEOPLE_CONTACT = 'peco'
|
||||
PEOPLE_CONTACT_GROUP = 'pecg'
|
||||
PEOPLE_PHOTO = 'peph'
|
||||
PEOPLE_PROFILE = 'pepr'
|
||||
PERMISSION = 'perm'
|
||||
PERMISSION_ID = 'peid'
|
||||
PERMITTEE = 'prmt'
|
||||
PERSONAL_DEVICE = 'pedv'
|
||||
PHOTO = 'phot'
|
||||
POLICY = 'poli'
|
||||
POP_ENABLED = 'popa'
|
||||
PRESENTATION = 'pres'
|
||||
PRINTER = 'prin'
|
||||
PRINTER_ID = 'prid'
|
||||
PRINTER_MODEL = 'prmd'
|
||||
PRIVILEGE = 'priv'
|
||||
PRODUCT = 'prod'
|
||||
PROFILE_SHARING_ENABLED = 'prof'
|
||||
PROJECT = 'proj'
|
||||
PROJECT_FOLDER = 'prjf'
|
||||
PROJECT_ID = 'prji'
|
||||
PUBLIC_KEY = 'pubk'
|
||||
QUERY = 'quer'
|
||||
RECIPIENT = 'recp'
|
||||
RECIPIENT_BCC = 'rebc'
|
||||
RECIPIENT_CC = 'recc'
|
||||
REPORT = 'rept'
|
||||
REQUEST_ID = 'reqi'
|
||||
RESOURCE_CALENDAR = 'resc'
|
||||
RESOURCE_ID = 'resi'
|
||||
ROLE = 'role'
|
||||
ROW = 'row '
|
||||
SCOPE = 'scop'
|
||||
SECTION = 'sect'
|
||||
SENDAS_ADDRESS = 'sasa'
|
||||
SENDER = 'send'
|
||||
SERVICE = 'serv'
|
||||
SHAREDDRIVE = 'tdrv'
|
||||
SHAREDDRIVE_ACL = 'tdac'
|
||||
SHAREDDRIVE_FOLDER = 'tdfo'
|
||||
SHAREDDRIVE_ID = 'tdid'
|
||||
SHAREDDRIVE_NAME = 'tdna'
|
||||
SHAREDDRIVE_THEME = 'tdth'
|
||||
SHEET = 'shet'
|
||||
SHEET_ID = 'shti'
|
||||
SIGNATURE = 'sign'
|
||||
SIZE = 'size'
|
||||
SKU = 'sku '
|
||||
SMIME_ID = 'smid'
|
||||
SNIPPETS_ENABLED = 'snip'
|
||||
SSO_KEY = 'ssok'
|
||||
SSO_SETTINGS = 'ssos'
|
||||
SOURCE_USER = 'srcu'
|
||||
SPREADSHEET = 'sprd'
|
||||
SPREADSHEET_RANGE = 'ssrn'
|
||||
START_TIME = 'strt'
|
||||
STATUS = 'stat'
|
||||
STUDENT = 'stud'
|
||||
SUBSCRIPTION = 'subs'
|
||||
SVCACCT = 'svac'
|
||||
SVCACCT_KEY = 'svky'
|
||||
TAGMANAGER_ACCOUNT = 'tmac'
|
||||
TAGMANAGER_CONTAINER = 'tmco'
|
||||
TAGMANAGER_PERMISSION = 'tmpm'
|
||||
TAGMANAGER_TAG = 'tmtg'
|
||||
TAGMANAGER_WORKSPACE = 'tmws'
|
||||
TARGET_USER = 'tgt '
|
||||
TASK = 'task'
|
||||
TASKLIST = 'tali'
|
||||
TEACHER = 'teac'
|
||||
THREAD = 'thre'
|
||||
TRANSFER_APPLICATION = 'trap'
|
||||
TRANSFER_ID = 'trid'
|
||||
TRANSFER_REQUEST = 'trnr'
|
||||
TRASHED_EVENT = 'trev'
|
||||
TRUSTED_APPLICATION = 'trus'
|
||||
TYPE = 'type'
|
||||
UNICODE_ENCODING_ENABLED = 'unic'
|
||||
UNIQUE_ID = 'uniq'
|
||||
URL = 'url '
|
||||
USER = 'user'
|
||||
USER_ALIAS = 'uali'
|
||||
USER_NOT_ARCHIVED = 'usna'
|
||||
USER_ARCHIVED = 'usar'
|
||||
USER_EMAIL = 'uema'
|
||||
USER_INVITATION = 'uinv'
|
||||
USER_NOT_SUSPENDED = 'usns'
|
||||
USER_SUSPENDED = 'usup'
|
||||
USER_SCHEMA = 'usch'
|
||||
VACATION = 'vaca'
|
||||
VACATION_ENABLED = 'vace'
|
||||
VALUE = 'val'
|
||||
VAULT_EXPORT = 'vlte'
|
||||
VAULT_HOLD = 'vlth'
|
||||
VAULT_MATTER = 'vltm'
|
||||
VAULT_MATTER_ARTIFACT = 'vlma'
|
||||
VAULT_MATTER_ID = 'vlmi'
|
||||
VAULT_OPERATION = 'vlto'
|
||||
VAULT_QUERY = 'vltq'
|
||||
WEB_MASTERSITE = 'wems'
|
||||
WEB_RESOURCE = 'were'
|
||||
WEBCLIPS_ENABLED = 'webc'
|
||||
YOUTUBE_CHANNEL = 'ytch'
|
||||
# _NAMES[0] is plural, _NAMES[1] is singular unless the item name is explicitly plural (Calendar Settings)
|
||||
# For items with Boolean values, both entries are singular (Forward, POP)
|
||||
# These values can be translated into other languages
|
||||
_NAMES = {
|
||||
ACCESS_TOKEN: ['Access Tokens', 'Access Token'],
|
||||
ACCOUNT: ['Google Workspace Accounts', 'Google Workspace Account'],
|
||||
ACTION: ['Actions', 'Action'],
|
||||
ACTIVITY: ['Activities', 'Activity'],
|
||||
ADMINISTRATOR: ['Administrators', 'Administrator'],
|
||||
ADMIN_ROLE: ['Admin Roles', 'Admin Role'],
|
||||
ADMIN_ROLE_ASSIGNMENT: ['Admin Role Assignments', 'Admin Role Assignment'],
|
||||
ALERT: ['Alerts', 'Alert'],
|
||||
ALERT_ID: ['Alert IDs', 'Alert ID'],
|
||||
ALERT_FEEDBACK: ['Alert Feedbacks', 'Alert Feedback'],
|
||||
ALERT_FEEDBACK_ID: ['Alert Feedback IDs', 'Alert Feedback ID'],
|
||||
ALERT_SETTINGS: ['Alert Settings', 'Alert Settings'],
|
||||
ALIAS: ['Aliases', 'Alias'],
|
||||
ALIAS_EMAIL: ['Alias Emails', 'Alias Email'],
|
||||
ALIAS_TARGET: ['Alias Targets', 'Alias Target'],
|
||||
ANALYTIC_ACCOUNT: ['Analytic Accounts', 'Analytic Account'],
|
||||
ANALYTIC_ACCOUNT_SUMMARY: ['Analytic Account Summaries', 'Analytic Account Summary'],
|
||||
ANALYTIC_DATASTREAM: ['Analytic Datastreams', 'Analytic Datastream'],
|
||||
ANALYTIC_PROPERTY: ['Analytic GA4 Properties', 'Analytic GA4 Property'],
|
||||
API: ['APIs', 'API'],
|
||||
APP_ACCESS_SETTINGS: ['Application Access Settings', 'Application Access Settings'],
|
||||
APP_ID: ['Application IDs', 'Application ID'],
|
||||
APP_NAME: ['Application Names', 'Application Name'],
|
||||
APPLICATION_SPECIFIC_PASSWORD: ['Application Specific Password IDs', 'Application Specific Password ID'],
|
||||
ARROWS_ENABLED: ['Personal Indicator Arrows Enabled', 'Personal Indicator Arrows Enabled'],
|
||||
ATTACHMENT: ['Attachments', 'Attachment'],
|
||||
ATTENDEE: ['Attendees', 'Attendee'],
|
||||
AUDIT_ACTIVITY_REQUEST: ['Audit Activity Requests', 'Audit Activity Request'],
|
||||
AUDIT_EXPORT_REQUEST: ['Audit Export Requests', 'Audit Export Request'],
|
||||
AUDIT_MONITOR_REQUEST: ['Audit Monitor Requests', 'Audit Monitor Request'],
|
||||
BACKUP_VERIFICATION_CODES: ['Backup Verification Codes', 'Backup Verification Codes'],
|
||||
BUILDING: ['Buildings', 'Building'],
|
||||
BUILDING_ID: ['Building IDs', 'Building ID'],
|
||||
BUSINESS_PROFILE_ACCOUNT: ['Business Profile Accounts', 'Business Profile Account'],
|
||||
CAA_LEVEL: ['CAA Levels', 'CAA Level'],
|
||||
CALENDAR: ['Calendars', 'Calendar'],
|
||||
CALENDAR_ACL: ['Calendar ACLs', 'Calendar ACL'],
|
||||
CALENDAR_SETTINGS: ['Calendar Settings', 'Calendar Settings'],
|
||||
CHANNEL_CUSTOMER: ['Channel Customers', 'Channel Customer'],
|
||||
CHANNEL_CUSTOMER_ENTITLEMENT: ['Channel Customer Entitlements', 'Channel Customer Entitlement'],
|
||||
CHANNEL_OFFER: ['Channel Offers', 'Channel Offer'],
|
||||
CHANNEL_PRODUCT: ['Channel Products', 'Channel Product'],
|
||||
CHANNEL_SKU: ['Channel SKUs', 'Channel SKU'],
|
||||
CHAT_BOT: ['Chat BOTs', 'Chat BOT'],
|
||||
CHAT_ADMIN: ['Chat Admins', 'Chat Admin'],
|
||||
CHAT_EMOJI: ['Chat Emojis', 'Chat Emoji'],
|
||||
CHAT_EVENT: ['Chat Events', 'Chat Event'],
|
||||
CHAT_MANAGER_USER: ['Chat User Managers', 'Chat User Manager'],
|
||||
CHAT_MESSAGE: ['Chat Messages', 'Chat Message'],
|
||||
CHAT_MESSAGE_ID: ['Chat Message IDs', 'Chat Message ID'],
|
||||
CHAT_MEMBER: ['Chat Members', 'Chat Member'],
|
||||
CHAT_MEMBER_GROUP: ['Chat Group Members', 'Chat Group Member'],
|
||||
CHAT_MEMBER_USER: ['Chat User Members', 'Chat User Member'],
|
||||
CHAT_OWNER_USER: ['Chat User Owners', 'Chat User Owner'],
|
||||
CHAT_SECTION: ['Chat User Sections', 'Chat User Section'],
|
||||
CHAT_SECTION_ITEM: ['Chat User Section Items', 'Chat User Section Item'],
|
||||
CHAT_SPACE: ['Chat Spaces', 'Chat Space'],
|
||||
CHAT_THREAD: ['Chat Threads', 'Chat Thread'],
|
||||
CHILD_ORGANIZATIONAL_UNIT: ['Child Organizational Units', 'Child Organizational Unit'],
|
||||
CHROME_APP: ['Chrome Applications', 'Chrome Application'],
|
||||
CHROME_APP_DEVICE: ['Chrome Application Devices', 'Chrome Application Device'],
|
||||
CHROME_BROWSER: ['Chrome Browsers', 'Chrome Browser'],
|
||||
CHROME_BROWSER_ENROLLMENT_TOKEN: ['Chrome Browser Enrollment Tokens', 'Chrome Browser Enrollment Token'],
|
||||
CHROME_CHANNEL: ['Chrome Channels', 'Chrome Channel'],
|
||||
CHROME_DEVICE: ['Chrome Devices', 'Chrome Device'],
|
||||
CHROME_DEVICE_COUNT: ['Chrome Device Counts', 'Chrome Device Count'],
|
||||
CHROME_MODEL: ['Chrome Models', 'Chrome Model'],
|
||||
CHROME_NETWORK_ID: ['Chrome Network IDs', 'Chrome Network ID'],
|
||||
CHROME_NETWORK_NAME: ['Chrome Network Names', 'Chrome Network Name'],
|
||||
CHROME_PLATFORM: ['Chrome Platforms', 'Chrome Platform'],
|
||||
CHROME_POLICY: ['Chrome Policies', 'Chrome Policy'],
|
||||
CHROME_POLICY_IMAGE: ['Chrome Policy Images', 'Chrome Policy Image'],
|
||||
CHROME_POLICY_SCHEMA: ['Chrome Policy Schemas', 'Chrome Policy Schema'],
|
||||
CHROME_PROFILE: ['Chrome Profiles', 'Chrome Profile'],
|
||||
CHROME_PROFILE_COMMAND: ['Chrome Profile Commands', 'Chrome Profile Command'],
|
||||
CHROME_RELEASE: ['Chrome Releases', 'Chrome Release'],
|
||||
CHROME_VERSION: ['Chrome Versions', 'Chrome Version'],
|
||||
CLASSIFICATION_LABEL: ['Classification Labels', 'Classification Label'],
|
||||
CLASSIFICATION_LABEL_FIELD_ID: ['Classification Label Field IDs', 'Classification Label Field ID'],
|
||||
CLASSIFICATION_LABEL_ID: ['Classification Label IDs', 'Classification Label ID'],
|
||||
CLASSIFICATION_LABEL_NAME: ['Classification Label Names', 'Classification Label Name'],
|
||||
CLASSIFICATION_LABEL_PERMISSION: ['Classification Label Permissions', 'Classification Label Permission'],
|
||||
CLASSIFICATION_LABEL_PERMISSION_NAME: ['Classification Label Permission Names', 'Classification Label Permission Name'],
|
||||
CLASSROOM_INVITATION: ['Classroom Invitations', 'Classroom Invitation'],
|
||||
CLASSROOM_INVITATION_OWNER: ['Classroom Owner Invitations', 'Classroom Owner Invitation'],
|
||||
CLASSROOM_INVITATION_STUDENT: ['Classroom Student Invitations', 'Classroom Student Invitation'],
|
||||
CLASSROOM_INVITATION_TEACHER: ['Classroom Teacher Invitations', 'Classroom Teacher Invitation'],
|
||||
CLASSROOM_OAUTH2_TXT_FILE: ['Classroom OAuth2 File', 'Classroom OAuth2 File'],
|
||||
CLASSROOM_USER_PROFILE: ['Classroom User Profile', 'Classroom User Profile'],
|
||||
CLIENT_ID: ['Client IDs', 'Client ID'],
|
||||
CLIENT_SECRETS_JSON_FILE: ['Client Secrets File', 'Client Secrets File'],
|
||||
CLOUD_IDENTITY_GROUP: ['Cloud Identity Groups', 'Cloud Identity Group'],
|
||||
CLOUD_STORAGE_BUCKET: ['Cloud Storage Buckets', 'Cloud Storage Bucket'],
|
||||
CLOUD_STORAGE_FILE: ['Cloud Storage Files', 'Cloud Storage File'],
|
||||
COLLABORATOR: ['Collaborators', 'Collaborator'],
|
||||
COMMAND_ID: ['Command IDs', 'Command ID'],
|
||||
COMPANY_DEVICE: ['Company Devices', 'Company Device'],
|
||||
CONFIG_FILE: ['Config File', 'Config File'],
|
||||
CONTACT: ['Contacts', 'Contact'],
|
||||
CONTACT_DELEGATE: ['Contact Delegates', 'Contact Delegate'],
|
||||
CONTACT_GROUP: ['Contact Groups', 'Contact Group'],
|
||||
CONTACT_GROUP_NAME: ['Contact Group Names', 'Contact Group Name'],
|
||||
COPYFROM_COURSE: ['Copy From Courses', 'CopyFrom Course'],
|
||||
COPYFROM_GROUP: ['Copy From Groups', 'CopyFrom Group'],
|
||||
COURSE: ['Courses', 'Course'],
|
||||
COURSE_ALIAS: ['Course Aliases', 'Course Alias'],
|
||||
COURSE_ANNOUNCEMENT: ['Course Announcements', 'Course Announcement'],
|
||||
COURSE_ANNOUNCEMENT_ID: ['Course Announcement IDs', 'Course Announcement ID'],
|
||||
COURSE_ANNOUNCEMENT_STATE: ['Course Announcement States', 'Course Announcement State'],
|
||||
COURSE_MATERIAL_DRIVEFILE: ['Course Material Drive Files', 'Course Material Drive File'],
|
||||
COURSE_MATERIAL_FORM: ['Course Material Forms', 'Course Material Form'],
|
||||
COURSE_MATERIAL: ['Course Materials', 'Course Material'],
|
||||
COURSE_MATERIAL_ID: ['Course Material IDs', 'Course Material ID'],
|
||||
COURSE_MATERIAL_STATE: ['Course Material States', 'Course Material State'],
|
||||
COURSE_NAME: ['Course Names', 'Course Name'],
|
||||
COURSE_STATE: ['Course States', 'Course State'],
|
||||
COURSE_STUDENTGROUP: ['Course Student Groups', 'Course Student Group'],
|
||||
COURSE_STUDENTGROUP_MEMBER: ['Course Student Group Members', 'Course Student Group Member'],
|
||||
COURSE_SUBMISSION_ID: ['Course Submission IDs', 'Course Submission ID'],
|
||||
COURSE_SUBMISSION_STATE: ['Course Submission States', 'Course Submission State'],
|
||||
COURSE_TOPIC: ['Course Topics', 'Course Topic'],
|
||||
COURSE_TOPIC_ID: ['Course Topic IDs', 'Course Topic ID'],
|
||||
COURSE_WORK: ['Course Works', 'Course Work'],
|
||||
COURSE_WORK_ID: ['Course Work IDs', 'Course Work ID'],
|
||||
COURSE_WORK_STATE: ['Course Work States', 'Course Work State'],
|
||||
CREATOR_ID: ['Creator IDs', 'Creator ID'],
|
||||
CREDENTIALS: ['Credentials', 'Credentials'],
|
||||
CRITERIA: ['Criteria', 'Criteria'],
|
||||
CROS_DEVICE: ['CrOS Devices', 'CrOS Device'],
|
||||
CROS_SERIAL_NUMBER: ['CrOS Serial Numbers', 'CrOS Serial Numbers'],
|
||||
CSE_IDENTITY: ['CSE Identities', 'CSE Identity'],
|
||||
CSE_KEYPAIR: ['CSE KeyPairs', 'CSE KeyPair'],
|
||||
CUSTOMER_DOMAIN: ['Customer Domains', 'Customer Domain'],
|
||||
CUSTOMER_ID: ['Customer IDs', 'Customer ID'],
|
||||
DATE: ['Dates', 'Date'],
|
||||
DEFAULT_LANGUAGE: ['Default Language', 'Default Language'],
|
||||
DELEGATE: ['Delegates', 'Delegate'],
|
||||
DELETED_USER: ['Deleted Users', 'Deleted User'],
|
||||
DELIVERY: ['Delivery', 'Delivery'],
|
||||
DEVICE: ['Devices', 'Device'],
|
||||
DEVICE_FILE: ['Device Files', 'Device File'],
|
||||
DEVICE_USER: ['Device Users', 'Device User'],
|
||||
DEVICE_USER_CLIENT_STATE: ['Device Users Client States', 'Device User Client State'],
|
||||
DIRECTORY: ['Directories', 'Directory'],
|
||||
DISCOVERY_JSON_FILE: ['Discovery File', 'Discovery File'],
|
||||
DOCUMENT: ['Documents', 'Document'],
|
||||
DOMAIN: ['Domains', 'Domain'],
|
||||
DOMAIN_ALIAS: ['Domain Aliases', 'Domain Alias'],
|
||||
DOMAIN_CONTACT: ['Domain Contacts', 'Domain Contact'],
|
||||
DOMAIN_PEOPLE_CONTACT: ['Domain People Contacts', 'Domain People Contact'],
|
||||
DOMAIN_PROFILE: ['Domain Profiles', 'Domain Profile'],
|
||||
DRIVE_DISK_USAGE: ['Drive Disk Usages', 'Drive Disk Usage'],
|
||||
DRIVE_FILE: ['Drive Files', 'Drive File'],
|
||||
DRIVE_FILE_COMMENT: ['Drive File Comments', 'Drive File Comment'],
|
||||
DRIVE_FILE_ID: ['Drive File IDs', 'Drive File ID'],
|
||||
DRIVE_FILE_NAME: ['Drive File Names', 'Drive File Name'],
|
||||
DRIVE_FILE_REVISION: ['Drive File Revisions', 'Drive File Revision'],
|
||||
DRIVE_FILE_RENAMED: ['Drive Files Renamed', 'Drive File Renamed'],
|
||||
DRIVE_FILE_SHORTCUT: ['Drive File Shortcuts', 'Drive File Shortcut'],
|
||||
DRIVE_FILE_OR_FOLDER: ['Drive Files/Folders', 'Drive File/Folder'],
|
||||
DRIVE_FILE_OR_FOLDER_ACL: ['Drive File/Folder ACLs', 'Drive File/Folder ACL'],
|
||||
DRIVE_FILE_OR_FOLDER_ID: ['Drive File/Folder IDs', 'Drive File/Folder ID'],
|
||||
DRIVE_FOLDER: ['Drive Folders', 'Drive Folder'],
|
||||
DRIVE_FOLDER_ID: ['Drive Folder IDs', 'Drive Folder ID'],
|
||||
DRIVE_FOLDER_NAME: ['Drive Folder Names', 'Drive Folder Name'],
|
||||
DRIVE_FOLDER_PATH: ['Drive Folder Paths', 'Drive Folder Path'],
|
||||
DRIVE_FOLDER_RENAMED: ['Drive Folders Renamed', 'Drive Folder Renamed'],
|
||||
DRIVE_FOLDER_SHORTCUT: ['Drive Folder Shortcuts', 'Drive Folder Shortcut'],
|
||||
DRIVE_ORPHAN_FILE_OR_FOLDER: ['Drive Orphan Files/Folders', 'Drive Orphan File/Folder'],
|
||||
DRIVE_PARENT_FOLDER: ['Drive Parent Folders', 'Drive Parent Folder'],
|
||||
DRIVE_PARENT_FOLDER_ID: ['Drive Parent Folder IDs', 'Drive Parent Folder ID'],
|
||||
DRIVE_PARENT_FOLDER_REFERENCE: ['Drive Parent Folder References', 'Drive Parent Folder Reference'],
|
||||
DRIVE_PATH: ['Drive Paths', 'Drive Path'],
|
||||
DRIVE_SETTINGS: ['Drive Settings', 'Drive Settings'],
|
||||
DRIVE_SHORTCUT: ['Drive Shortcuts', 'Drive Shortcut'],
|
||||
DRIVE_SHORTCUT_ID: ['Drive Shortcut IDs', 'Drive Shortcut ID'],
|
||||
DRIVE_3PSHORTCUT: ['Drive 3rd Party Shortcuts', 'Drive 3rd Party Shortcut'],
|
||||
DRIVE_TRASH: ['Drive Trash', 'Drive Trash'],
|
||||
EMAIL: ['Email Addresses', 'Email Address'],
|
||||
EMAIL_ALIAS: ['Email Aliases', 'Email Alias'],
|
||||
EMAIL_SETTINGS: ['Email Settings', 'Email Settings'],
|
||||
END_TIME: ['End Times', 'End Time'],
|
||||
ENTITY: ['Entities', 'Entity'],
|
||||
EVENT: ['Events', 'Event'],
|
||||
EVENT_BIRTHDAY: ['Borthday Events', 'Birthday Event'],
|
||||
EVENT_FOCUSTIME: ['Focus Time Events', 'Focus Time Event'],
|
||||
EVENT_OUTOFOFFICE: ['Out of Office Events', 'Out of Office Event'],
|
||||
EVENT_WORKINGLOCATION: ['Working Location Events', 'Working Location Event'],
|
||||
FEATURE: ['Features', 'Feature'],
|
||||
FIELD: ['Fields', 'Field'],
|
||||
FILE: ['Files', 'File'],
|
||||
FILE_PARENT_TREE: ['File Parent Trees', 'File Parent Tree'],
|
||||
FILTER: ['Filters', 'Filter'],
|
||||
FORM: ['Forms', 'Form'],
|
||||
FORM_RESPONSE: ['Form Responses', 'Form Response'],
|
||||
FORWARD_ENABLED: ['Forward Enabled', 'Forward Enabled'],
|
||||
FORWARDING_ADDRESS: ['Forwarding Addresses', 'Forwarding Address'],
|
||||
GCP_FOLDER: ['GCP Folders', 'GCP Folder'],
|
||||
GCP_FOLDER_NAME: ['GCP Folder Names', 'GCP Folder Name'],
|
||||
GCP_ORG_ID: ['GCP Organization ID', 'GCP Organization ID'],
|
||||
GMAIL_PROFILE: ['Gmail Profile', 'Gmail Profile'],
|
||||
GROUP: ['Groups', 'Group'],
|
||||
GROUP_ALIAS: ['Group Aliases', 'Group Alias'],
|
||||
GROUP_EMAIL: ['Group Emails', 'Group Email'],
|
||||
GROUP_MEMBERSHIP: ['Group Memberships', 'Group Membership'],
|
||||
GROUP_MEMBERSHIP_TREE: ['Group Membership Trees', 'Group Membership Tree'],
|
||||
GROUP_SETTINGS: ['Group Settings', 'Group Settings'],
|
||||
GROUP_TREE: ['Group Trees', 'Group Tree'],
|
||||
GUEST_USER: ['Guest Users', 'Guest User'],
|
||||
GUARDIAN: ['Guardians', 'Guardian'],
|
||||
GUARDIAN_INVITATION: ['Guardian Invitations', 'Guardian Invitation'],
|
||||
GUARDIAN_AND_INVITATION: ['Guardians and Invitations', 'Guardian and Invitation'],
|
||||
IAM_POLICY: ['IAM Policies', 'IAM Policy'],
|
||||
IMAP_ENABLED: ['IMAP Enabled', 'IMAP Enabled'],
|
||||
INBOUND_SSO_ASSIGNMENT: ['Inbound SSO Assignments', 'Inbound SSO Assignment'],
|
||||
INBOUND_SSO_CREDENTIALS: ['Inbound SSO Credentials', 'Inbound SSO Credential'],
|
||||
INBOUND_SSO_PROFILE: ['Inbound SSO Profiles', 'Inbound SSO Profile'],
|
||||
INSTANCE: ['Instances', 'Instance'],
|
||||
ISSUER_CN: ['Issuer CNs', 'Issuer CN'],
|
||||
ITEM: ['Items', 'Item'],
|
||||
KEYBOARD_SHORTCUTS_ENABLED: ['Keyboard Shortcuts Enabled', 'Keyboard Shortcuts Enabled'],
|
||||
LABEL: ['Labels', 'Label'],
|
||||
LABEL_ID: ['Label IDs', 'Label ID'],
|
||||
LANGUAGE: ['Languages', 'Language'],
|
||||
LICENSE: ['Licenses', 'License'],
|
||||
LOCATION: ['Locations', 'Location'],
|
||||
LOOKERSTUDIO_ASSET: ['Looker Studio Assets', 'Looker Studio Asset'],
|
||||
LOOKERSTUDIO_ASSET_DATASOURCE: ['Looker Studio DATA_SOURCE Assets', 'Looker Studio DATA_SOURCE Asset'],
|
||||
LOOKERSTUDIO_ASSETID: ['Looker Studio Asset IDs', 'Looker Studio Asset ID'],
|
||||
LOOKERSTUDIO_ASSET_REPORT: ['Looker Studio REPORT Assets', 'Looker Studio REPORT Asset'],
|
||||
LOOKERSTUDIO_PERMISSION: ['Looker Studio Permissions', 'Looker Studio Permission'],
|
||||
MD5HASH: ['MD5 hash', 'MD5 Hash'],
|
||||
MEET_SPACE: ['Meet Spaces', 'Meet Space'],
|
||||
MEET_CONFERENCE: ['Meet Conferences', 'Meet Conference'],
|
||||
MEET_PARTICIPANT: ['Meet Participants', 'Meet Participant'],
|
||||
MEET_RECORDING: ['Meet Recordings', 'Meet Recording'],
|
||||
MEET_TRANSCRIPT: ['Meet Transcripts', 'Meet Transcript'],
|
||||
MEMBER: ['Members', 'Member'],
|
||||
MEMBER_NOT_ARCHIVED: ['Members (Not Archived)', 'Member (Not Archived)'],
|
||||
MEMBER_ARCHIVED: ['Members (Archived)', 'Member (Archived)'],
|
||||
MEMBER_NOT_SUSPENDED: ['Members (Not Suspended)', 'Member (Not Suspended)'],
|
||||
MEMBER_SUSPENDED: ['Members (Suspended)', 'Member (Suspended)'],
|
||||
MEMBER_NOT_SUSPENDED_NOT_ARCHIVED: ['Members (Not Suspended & Not Archived)', 'Member (Not Suspended & Not Archived)'],
|
||||
MEMBER_SUSPENDED_ARCHIVED: ['Members (Suspended & Archived)', 'Member (Suspended & Archived)'],
|
||||
MEMBER_RESTRICTION: ['Member Restrictions', 'Member Restriction'],
|
||||
MEMBER_URI: ['Member URIs', 'Member URI'],
|
||||
MEMBERSHIP_TREE: ['Membership Trees', 'Membership Tree'],
|
||||
MESSAGE: ['Messages', 'Message'],
|
||||
MIMETYPE: ['MIME Types', 'MIME Type'],
|
||||
MOBILE_DEVICE: ['Mobile Devices', 'Mobile Device'],
|
||||
NAME: ['Names', 'Name'],
|
||||
NONEDITABLE_ALIAS: ['Non-Editable Aliases', 'Non-Editable Alias'],
|
||||
NOTE: ['Notes', 'Note'],
|
||||
NOTE_ACL: ['Note ACLs', 'Note ACL'],
|
||||
NOTES_ACLS: ["'Note's ACLs", "Note's ACLs"],
|
||||
NOTIFICATION: ['Notifications', 'Notification'],
|
||||
OAUTH2_TXT_FILE: ['Client OAuth2 File', 'Client OAuth2 File'],
|
||||
OAUTH2SERVICE_JSON_FILE: ['Service Account OAuth2 File', 'Service Account OAuth2 File'],
|
||||
ORGANIZATIONAL_UNIT: ['Organizational Units', 'Organizational Unit'],
|
||||
OTHER_CONTACT: ['Other Contacts', 'Other Contact'],
|
||||
OWNER: ['Owners', 'Owner'],
|
||||
OWNER_ID: ['Owner IDs', 'Owner ID'],
|
||||
PAGE_SIZE: ['Page Size', 'Page Size'],
|
||||
PARENT_ORGANIZATIONAL_UNIT: ['Parent Organizational Units', 'Parent Organizational Unit'],
|
||||
PARTICIPANT: ['Participants', 'Participant'],
|
||||
PEOPLE_CONTACT: ['People Contacts', 'Person Contact'],
|
||||
PEOPLE_CONTACT_GROUP: ['People Contact Groups', 'People Contact Group'],
|
||||
PEOPLE_PHOTO: ['People Photos', 'Person Photo'],
|
||||
PEOPLE_PROFILE: ['People Profiles', 'People Profile'],
|
||||
PERMISSION: ['Permissions', 'Permission'],
|
||||
PERMISSION_ID: ['Permission IDs', 'Permission ID'],
|
||||
PERMITTEE: ['Permittees', 'Permittee'],
|
||||
PERSONAL_DEVICE: ['Personal Devices', 'Personal Device'],
|
||||
PHOTO: ['Photos', 'Photo'],
|
||||
POLICY: ['Policies', 'Policy'],
|
||||
POP_ENABLED: ['POP Enabled', 'POP Enabled'],
|
||||
PRESENTATION: ['Presentations', 'Presentation'],
|
||||
PRINTER: ['Printers', 'Printer'],
|
||||
PRINTER_ID: ['Printer IDs', 'Printer ID'],
|
||||
PRINTER_MODEL: ['Printer Models', 'Printer Model'],
|
||||
PRIVILEGE: ['Privileges', 'Privilege'],
|
||||
PRODUCT: ['Products', 'Product'],
|
||||
PROFILE_SHARING_ENABLED: ['Profile Sharing Enabled', 'Profile Sharing Enabled'],
|
||||
PROJECT: ['Projects', 'Project'],
|
||||
PROJECT_FOLDER: ['Project Folders', 'Project Folder'],
|
||||
PROJECT_ID: ['Project IDs', 'Project ID'],
|
||||
PUBLIC_KEY: ['Public Key', 'Public Key'],
|
||||
QUERY: ['Queries', 'Query'],
|
||||
RECIPIENT: ['Recipients', 'Recipient'],
|
||||
RECIPIENT_BCC: ['Recipients (BCC)', 'Recipient (BCC)'],
|
||||
RECIPIENT_CC: ['Recipients (CC)', 'Recipient (CC)'],
|
||||
REPORT: ['Reports', 'Report'],
|
||||
REQUEST_ID: ['Request IDs', 'Request ID'],
|
||||
RESOURCE_CALENDAR: ['Resource Calendars', 'Resource Calendar'],
|
||||
RESOURCE_ID: ['Resource IDs', 'Resource ID'],
|
||||
ROLE: ['Roles', 'Role'],
|
||||
ROW: ['Rows', 'Row'],
|
||||
SCOPE: ['Scopes', 'Scope'],
|
||||
SECTION: ['Sections', 'Section'],
|
||||
SENDAS_ADDRESS: ['SendAs Addresses', 'SendAs Address'],
|
||||
SENDER: ['Senders', 'Sender'],
|
||||
SERVICE: ['Services', 'Service'],
|
||||
SHAREDDRIVE: ['Shared Drives', 'Shared Drive'],
|
||||
SHAREDDRIVE_ACL: ['Shared Drive ACLs', 'Shared Drive ACL'],
|
||||
SHAREDDRIVE_FOLDER: ['Shared Drive Folders', 'Shared Drive Folder'],
|
||||
SHAREDDRIVE_ID: ['Shared Drive IDs', 'Shared Drive ID'],
|
||||
SHAREDDRIVE_NAME: ['Shared Drive Names', 'Shared Drive Name'],
|
||||
SHAREDDRIVE_THEME: ['Shared Drive Themes', 'Shared Drive Theme'],
|
||||
SHEET: ['Sheets', 'Sheet'],
|
||||
SHEET_ID: ['Sheet IDs', 'Sheet ID'],
|
||||
SIGNATURE: ['Signatures', 'Signature'],
|
||||
SIZE: ['Sizes', 'Size'],
|
||||
SKU: ['SKUs', 'SKU'],
|
||||
SMIME_ID: ['S/MIME Certificate IDs', 'S/MIME Certificate ID'],
|
||||
SNIPPETS_ENABLED: ['Preview Snippets Enabled', 'Preview Snippets Enabled'],
|
||||
SSO_KEY: ['SSO Key', 'SSO Key'],
|
||||
SSO_SETTINGS: ['SSO Settings', 'SSO Settings'],
|
||||
SOURCE_USER: ['Source Users', 'Source User'],
|
||||
SPREADSHEET: ['Spreadsheets', 'Spreadsheet'],
|
||||
SPREADSHEET_RANGE: ['Spreadsheet Ranges', 'Spreadsheet Range'],
|
||||
START_TIME: ['Start Times', 'Start Time'],
|
||||
STATUS: ['Status', 'Status'],
|
||||
STUDENT: ['Students', 'Student'],
|
||||
SUBSCRIPTION: ['Subscriptions', 'Subscription'],
|
||||
SVCACCT: ['Service Accounts', 'Service Account'],
|
||||
SVCACCT_KEY: ['Service Account Keys', 'Service Account Key'],
|
||||
TAGMANAGER_ACCOUNT: ['Tag Manager Accounts', 'Tag Manager Account'],
|
||||
TAGMANAGER_CONTAINER: ['Tag Manager Containers', 'Tag Manager Container'],
|
||||
TAGMANAGER_PERMISSION: ['Tag Manager Permissions', 'Tag Manager Permission'],
|
||||
TAGMANAGER_TAG: ['Tag Manager Tags', 'Tag Manager Tag'],
|
||||
TAGMANAGER_WORKSPACE: ['Tag Manager Workspaces', 'Tag Manager Workspace'],
|
||||
TARGET_USER: ['Target Users', 'Target User'],
|
||||
TASK: ['Tasks', 'Task'],
|
||||
TASKLIST: ['Tasklists', 'Tasklist'],
|
||||
TEACHER: ['Teachers', 'Teacher'],
|
||||
THREAD: ['Threads', 'Thread'],
|
||||
TRANSFER_APPLICATION: ['Transfer Applications', 'Transfer Application'],
|
||||
TRANSFER_ID: ['Transfer IDs', 'Transfer ID'],
|
||||
TRANSFER_REQUEST: ['Transfer Requests', 'Transfer Request'],
|
||||
TRASHED_EVENT: ['Trashed Events', 'Trashed Event'],
|
||||
TRUSTED_APPLICATION: ['Trusted Applications', 'Trusted Application'],
|
||||
TYPE: ['Types', 'Type'],
|
||||
UNICODE_ENCODING_ENABLED: ['UTF-8 Encoding Enabled', 'UTF-8 Encoding Enabled'],
|
||||
UNIQUE_ID: ['Unique IDs', 'Unique ID'],
|
||||
URL: ['URLs', 'URL'],
|
||||
USER: ['Users', 'User'],
|
||||
USER_ALIAS: ['User Aliases', 'User Alias'],
|
||||
USER_NOT_ARCHIVED: ['Users (Not archived)', 'User (Not archived)'],
|
||||
USER_ARCHIVED: ['Users (Archived)', 'User (Archived)'],
|
||||
USER_EMAIL: ['User Emails', 'User Email'],
|
||||
USER_INVITATION: ['User Invitations', 'User Invitation'],
|
||||
USER_NOT_SUSPENDED: ['Users (Not suspended)', 'User (Not suspended)'],
|
||||
USER_SUSPENDED: ['Users (Suspended)', 'User (Suspended)'],
|
||||
USER_SCHEMA: ['Schemas', 'Schema'],
|
||||
VACATION: ['Vacation', 'Vacation'],
|
||||
VACATION_ENABLED: ['Vacation Enabled', 'Vacation Enabled'],
|
||||
VALUE: ['Values', 'Value'],
|
||||
VAULT_EXPORT: ['Vault Exports', 'Vault Export'],
|
||||
VAULT_HOLD: ['Vault Holds', 'Vault Hold'],
|
||||
VAULT_MATTER: ['Vault Matters', 'Vault Matter'],
|
||||
VAULT_MATTER_ARTIFACT: ['Vault Matter Artifacts', 'Vault Matter Artifact'],
|
||||
VAULT_MATTER_ID: ['Vault Matter IDs', 'Vault Matter ID'],
|
||||
VAULT_OPERATION: ['Vault Operations', 'Vault Operation'],
|
||||
VAULT_QUERY: ['Vault Queries', 'Vault Query'],
|
||||
WEBCLIPS_ENABLED: ['Web Clips Enabled', 'Web Clips Enabled'],
|
||||
WEB_MASTERSITE: ['Web Master Sites', 'Web Master Site'],
|
||||
WEB_RESOURCE: ['Web Resources', 'Web Resource'],
|
||||
YOUTUBE_CHANNEL: ['YouTube Channels', 'YouTube Channel'],
|
||||
ROLE_MANAGER: ['Managers', 'Manager'],
|
||||
ROLE_MEMBER: ['Members', 'Member'],
|
||||
ROLE_OWNER: ['Owners', 'Owner'],
|
||||
ROLE_ALL: ['Members, Managers, Owners', 'Member, Manager, Owner'],
|
||||
ROLE_USER: ['Users', 'User'],
|
||||
ROLE_MANAGER_MEMBER: ['Members, Managers', 'Member, Manager'],
|
||||
ROLE_MANAGER_OWNER: ['Managers, Owners', 'Manager, Owner'],
|
||||
ROLE_MEMBER_OWNER: ['Members, Owners', 'Member, Owner'],
|
||||
ROLE_MANAGER_MEMBER_OWNER: ['Members, Managers, Owners', 'Member, Manager, Owner'],
|
||||
ROLE_PUBLIC: ['Public', 'Public'],
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.entityType = None
|
||||
self.forWhom = None
|
||||
self.preQualifier = ''
|
||||
self.postQualifier = ''
|
||||
|
||||
def SetGetting(self, entityType):
|
||||
self.entityType = entityType
|
||||
self.preQualifier = self.postQualifier = ''
|
||||
|
||||
def SetGettingQuery(self, entityType, query):
|
||||
self.entityType = entityType
|
||||
self.preQualifier = f' that match query ({query})'
|
||||
self.postQualifier = f' that matched query ({query})'
|
||||
|
||||
def SetGettingQualifier(self, entityType, qualifier):
|
||||
self.entityType = entityType
|
||||
self.preQualifier = self.postQualifier = qualifier
|
||||
|
||||
def Getting(self):
|
||||
return self.entityType
|
||||
|
||||
def GettingPreQualifier(self):
|
||||
return self.preQualifier
|
||||
|
||||
def GettingPostQualifier(self):
|
||||
return self.postQualifier
|
||||
|
||||
def SetGettingForWhom(self, forWhom):
|
||||
self.forWhom = forWhom
|
||||
|
||||
def GettingForWhom(self):
|
||||
return self.forWhom
|
||||
|
||||
def Choose(self, entityType, count):
|
||||
return self._NAMES[entityType][[0, 1][count == 1]]
|
||||
|
||||
def ChooseGetting(self, count):
|
||||
return self._NAMES[self.entityType][[0, 1][count == 1]]
|
||||
|
||||
def Plural(self, entityType):
|
||||
return self._NAMES[entityType][0]
|
||||
|
||||
def PluralGetting(self):
|
||||
return self._NAMES[self.entityType][0]
|
||||
|
||||
def Singular(self, entityType):
|
||||
return self._NAMES[entityType][1]
|
||||
|
||||
def SingularGetting(self):
|
||||
return self._NAMES[self.entityType][1]
|
||||
|
||||
def MayTakeTime(self, entityType):
|
||||
if entityType:
|
||||
return f', may take some time on a large {self.Singular(entityType)}...'
|
||||
return ''
|
||||
|
||||
def FormatEntityValueList(self, entityValueList):
|
||||
evList = []
|
||||
for j in range(0, len(entityValueList), 2):
|
||||
evList.append(self.Singular(entityValueList[j]))
|
||||
evList.append(entityValueList[j+1])
|
||||
return evList
|
||||
|
||||
def TypeMessage(self, entityType, message):
|
||||
return f'{self.Singular(entityType)}: {message}'
|
||||
|
||||
def TypeName(self, entityType, entityName):
|
||||
return f'{self.Singular(entityType)}: {entityName}'
|
||||
|
||||
def TypeNameMessage(self, entityType, entityName, message):
|
||||
return f'{self.Singular(entityType)}: {entityName} {message}'
|
||||
@@ -1,866 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2026 Ross Scroggs All Rights Reserved.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""GAM GAPI resources
|
||||
|
||||
"""
|
||||
# callGAPI throw reasons
|
||||
ABORTED = 'aborted'
|
||||
ABUSIVE_CONTENT_RESTRICTION = 'abusiveContentRestriction'
|
||||
ACCESS_NOT_CONFIGURED = 'accessNotConfigured'
|
||||
ADMIN_CANNOT_UNSUSPEND = 'adminCannotUnsuspend'
|
||||
ALREADY_EXISTS = 'alreadyExists'
|
||||
APPLY_LABEL_FORBIDDEN = 'applyLabelForbidden'
|
||||
AUTH_ERROR = 'authError'
|
||||
BACKEND_ERROR = 'backendError'
|
||||
BAD_GATEWAY = 'badGateway'
|
||||
BAD_REQUEST = 'badRequest'
|
||||
CANNOT_ADD_PARENT = 'cannotAddParent'
|
||||
CANNOT_CHANGE_ORGANIZER = 'cannotChangeOrganizer'
|
||||
CANNOT_CHANGE_ORGANIZER_OF_INSTANCE = 'cannotChangeOrganizerOfInstance'
|
||||
CANNOT_CHANGE_OWN_ACL = 'cannotChangeOwnAcl'
|
||||
CANNOT_CHANGE_OWNER_ACL = 'cannotChangeOwnerAcl'
|
||||
CANNOT_CHANGE_OWN_PRIMARY_SUBSCRIPTION = 'cannotChangeOwnPrimarySubscription'
|
||||
CANNOT_COPY_FILE = 'cannotCopyFile'
|
||||
CANNOT_DELETE_ONLY_REVISION = 'cannotDeleteOnlyRevision'
|
||||
CANNOT_DELETE_PERMISSION = 'cannotDeletePermission'
|
||||
CANNOT_DELETE_PRIMARY_CALENDAR = 'cannotDeletePrimaryCalendar'
|
||||
CANNOT_DELETE_PRIMARY_SENDAS = 'cannotDeletePrimarySendAs'
|
||||
CANNOT_DELETE_RESOURCE_WITH_CHILDREN = 'cannotDeleteResourceWithChildren'
|
||||
CANNOT_MODIFY_ACL_OF_CALENDAR_OWNER = 'cannotModifyAclOfCalendarOwner'
|
||||
CANNOT_MODIFY_INHERITED_PERMISSION = 'cannotModifyInheritedPermission'
|
||||
CANNOT_MODIFY_INHERITED_TEAMDRIVE_PERMISSION = 'cannotModifyInheritedTeamDrivePermission'
|
||||
CANNOT_MODIFY_RESTRICTED_LABEL = 'cannotModifyRestrictedLabel'
|
||||
CANNOT_MODIFY_VIEWERS_CAN_COPY_CONTENT = 'cannotModifyViewersCanCopyContent'
|
||||
CANNOT_MOVE_TRASHED_ITEM_INTO_TEAMDRIVE = 'cannotMoveTrashedItemIntoTeamDrive'
|
||||
CANNOT_MOVE_TRASHED_ITEM_OUT_OF_TEAMDRIVE = 'cannotMoveTrashedItemOutOfTeamDrive'
|
||||
CANNOT_REMOVE_OWNER = 'cannotRemoveOwner'
|
||||
CANNOT_SET_EXPIRATION = 'cannotSetExpiration'
|
||||
CANNOT_SET_EXPIRATION_ON_ANYONE_OR_DOMAIN = 'cannotSetExpirationOnAnyoneOrDomain'
|
||||
CANNOT_SHARE_GROUPS_WITHLINK = 'cannotShareGroupsWithLink'
|
||||
CANNOT_SHARE_USERS_WITHLINK = 'cannotShareUsersWithLink'
|
||||
CANNOT_SHARE_TEAMDRIVE_TOPFOLDER_WITH_ANYONEORDOMAINS = 'cannotShareTeamDriveTopFolderWithAnyoneOrDomains'
|
||||
CANNOT_SHARE_TEAMDRIVE_WITH_NONGOOGLE_ACCOUNTS = 'cannotShareTeamDriveWithNonGoogleAccounts'
|
||||
CANNOT_UNSUBSCRIBE_FROM_OWNED_CALENDAR = 'cannotUnsubscribeFromOwnedCalendar'
|
||||
CANNOT_UPDATE_PERMISSION = 'cannotUpdatePermission'
|
||||
CONDITION_NOT_MET = 'conditionNotMet'
|
||||
CONFLICT = 'conflict'
|
||||
CONTENT_OWNER_ACCOUNT_NOT_FOUND = 'contentOwnerAccountNotFound'
|
||||
CROSS_DOMAIN_MOVE_RESTRICTION = 'crossDomainMoveRestriction'
|
||||
CUSTOMER_EXCEEDED_ROLE_ASSIGNMENTS_LIMIT = 'CUSTOMER_EXCEEDED_ROLE_ASSIGNMENTS_LIMIT'
|
||||
CUSTOMER_NOT_FOUND = 'customerNotFound'
|
||||
CYCLIC_MEMBERSHIPS_NOT_ALLOWED = 'cyclicMembershipsNotAllowed'
|
||||
DAILY_LIMIT_EXCEEDED = 'dailyLimitExceeded'
|
||||
DELETED = 'deleted'
|
||||
DELETED_USER_NOT_FOUND = 'deletedUserNotFound'
|
||||
DOMAIN_ALIAS_NOT_FOUND = 'domainAliasNotFound'
|
||||
DOMAIN_CANNOT_USE_APIS = 'domainCannotUseApis'
|
||||
DOMAIN_NOT_FOUND = 'domainNotFound'
|
||||
DOMAIN_NOT_VERIFIED_SECONDARY = 'domainNotVerifiedSecondary'
|
||||
DOMAIN_POLICY = 'domainPolicy'
|
||||
DOWNLOAD_QUOTA_EXCEEDED = 'downloadQuotaExceeded'
|
||||
DUPLICATE = 'duplicate'
|
||||
EVENT_DURATION_EXCEEDS_LIMIT = 'eventDurationExceedsLimit'
|
||||
EVENT_TYPE_RESTRICTION = 'eventTypeRestriction'
|
||||
EXPIRATION_DATES_MUST_BE_IN_THE_FUTURE = 'expirationDatesMustBeInTheFuture'
|
||||
EXPIRATION_DATE_NOT_ALLOWED_FOR_SHARED_DRIVE_MEMBERS = 'expirationDateNotAllowedForSharedDriveMembers'
|
||||
FAILED_PRECONDITION = 'failedPrecondition'
|
||||
FIELD_IN_USE = 'fieldInUse'
|
||||
FIELD_NOT_WRITABLE = 'fieldNotWritable'
|
||||
FILE_NEVER_WRITABLE = 'fileNeverWritable'
|
||||
FILE_NOT_FOUND = 'fileNotFound'
|
||||
FILE_ORGANIZER_NOT_YET_ENABLED_FOR_THIS_TEAMDRIVE = 'fileOrganizerNotYetEnabledForThisTeamDrive'
|
||||
FILE_ORGANIZER_ON_FOLDERS_IN_SHARED_DRIVE_ONLY = 'fileOrganizerOnFoldersInSharedDriveOnly'
|
||||
FILE_ORGANIZER_ON_NON_TEAMDRIVE_NOT_SUPPORTED = 'fileOrganizerOnNonTeamDriveNotSupported'
|
||||
FILE_OWNER_NOT_MEMBER_OF_TEAMDRIVE = 'fileOwnerNotMemberOfTeamDrive'
|
||||
FILE_OWNER_NOT_MEMBER_OF_WRITER_DOMAIN = 'fileOwnerNotMemberOfWriterDomain'
|
||||
FILE_WRITER_TEAMDRIVE_MOVE_IN_DISABLED = 'fileWriterTeamDriveMoveInDisabled'
|
||||
FORBIDDEN = 'forbidden'
|
||||
GATEWAY_TIMEOUT = 'gatewayTimeout'
|
||||
GROUP_NOT_FOUND = 'groupNotFound'
|
||||
ILLEGAL_ACCESS_ROLE_FOR_DEFAULT = 'illegalAccessRoleForDefault'
|
||||
INSUFFICIENT_ADMINISTRATOR_PRIVILEGES = 'insufficientAdministratorPrivileges'
|
||||
INSUFFICIENT_ARCHIVED_USER_LICENSES = 'insufficientArchivedUserLicenses'
|
||||
INSUFFICIENT_FILE_PERMISSIONS = 'insufficientFilePermissions'
|
||||
INSUFFICIENT_PARENT_PERMISSIONS = 'insufficientParentPermissions'
|
||||
INSUFFICIENT_PERMISSIONS = 'insufficientPermissions'
|
||||
INTERNAL_ERROR = 'internalError'
|
||||
INVALID = 'invalid'
|
||||
INVALID_ARGUMENT = 'invalidArgument'
|
||||
INVALID_ATTRIBUTE_VALUE = 'invalidAttributeValue'
|
||||
INVALID_CUSTOMER_ID = 'invalidCustomerId'
|
||||
INVALID_INPUT = 'invalidInput'
|
||||
INVALID_LINK_VISIBILITY = 'invalidLinkVisibility'
|
||||
INVALID_MEMBER = 'invalidMember'
|
||||
INVALID_MESSAGE_ID = 'invalidMessageId'
|
||||
INVALID_ORGUNIT = 'invalidOrgunit'
|
||||
INVALID_ORGUNIT_NAME = 'invalidOrgunitName'
|
||||
INVALID_OWNERSHIP_TRANSFER = 'invalidOwnershipTransfer'
|
||||
INVALID_PARAMETER = 'invalidParameter'
|
||||
INVALID_PARENT_ORGUNIT = 'invalidParentOrgunit'
|
||||
INVALID_QUERY = 'invalidQuery'
|
||||
INVALID_RESOURCE = 'invalidResource'
|
||||
INVALID_SCHEMA_VALUE = 'invalidSchemaValue'
|
||||
INVALID_SCOPE_VALUE = 'invalidScopeValue'
|
||||
INVALID_SHARING_REQUEST = 'invalidSharingRequest'
|
||||
LABEL_MULTIPLE_VALUES_FOR_SINGULAR_FIELD = 'labelMultipleValuesForSingularField'
|
||||
LABEL_MUTATION_FORBIDDEN = 'labelMutationForbidden'
|
||||
LABEL_MUTATION_ILLEGAL_SELECTION = 'labelMutationIllegalSelection'
|
||||
LABEL_MUTATION_UNKNOWN_FIELD = 'labelMutationUnknownField'
|
||||
LIMIT_EXCEEDED = 'limitExceeded'
|
||||
LOGIN_REQUIRED = 'loginRequired'
|
||||
MALFORMED_WORKING_LOCATION_EVENT = 'malformedWorkingLocationEvent'
|
||||
MEMBER_NOT_FOUND = 'memberNotFound'
|
||||
MYDRIVE_HIERARCHY_DEPTH_LIMIT_EXCEEDED = 'myDriveHierarchyDepthLimitExceeded'
|
||||
NO_LIST_TEAMDRIVES_ADMINISTRATOR_PRIVILEGE = 'noListTeamDrivesAdministratorPrivilege'
|
||||
NO_MANAGE_TEAMDRIVE_ADMINISTRATOR_PRIVILEGE = 'noManageTeamDriveAdministratorPrivilege'
|
||||
NOT_A_CALENDAR_USER = 'notACalendarUser'
|
||||
NOT_FOUND = 'notFound'
|
||||
NOT_IMPLEMENTED = 'notImplemented'
|
||||
NUM_CHILDREN_IN_NON_ROOT_LIMIT_EXCEEDED = 'numChildrenInNonRootLimitExceeded'
|
||||
OPERATION_NOT_SUPPORTED = 'operationNotSupported'
|
||||
ORGANIZER_ON_NON_TEAMDRIVE_NOT_SUPPORTED = 'organizerOnNonTeamDriveNotSupported'
|
||||
ORGANIZER_ON_NON_TEAMDRIVE_ITEM_NOT_SUPPORTED = 'organizerOnNonTeamDriveItemNotSupported'
|
||||
ORGUNIT_NOT_FOUND = 'orgunitNotFound'
|
||||
OUTSIDE_DOMAIN_MEMBER_CANNOT_CHANGE_TEAMDRIVE_RESTRICTIONS = 'outsideDomainMemberCannotChangeTeamDriveRestrictions'
|
||||
OWNER_ON_TEAMDRIVE_ITEM_NOT_SUPPORTED = 'ownerOnTeamDriveItemNotSupported'
|
||||
OWNERSHIP_CHANGE_ACROSS_DOMAIN_NOT_PERMITTED = 'ownershipChangeAcrossDomainNotPermitted'
|
||||
PARTICIPANT_IS_NEITHER_ORGANIZER_NOR_ATTENDEE = 'participantIsNeitherOrganizerNorAttendee'
|
||||
PERMISSION_DENIED = 'permissionDenied'
|
||||
PERMISSION_NOT_FOUND = 'permissionNotFound'
|
||||
PHOTO_NOT_FOUND = 'photoNotFound'
|
||||
PUBLISH_OUT_NOT_PERMITTED = 'publishOutNotPermitted'
|
||||
QUERY_REQUIRES_ADMIN_CREDENTIALS = 'queryRequiresAdminCredentials'
|
||||
QUOTA_EXCEEDED = 'quotaExceeded'
|
||||
RATE_LIMIT_EXCEEDED = 'rateLimitExceeded'
|
||||
REQUIRED = 'required'
|
||||
REQUIRED_ACCESS_LEVEL = 'requiredAccessLevel'
|
||||
RESOURCE_EXHAUSTED = 'resourceExhausted'
|
||||
RESOURCE_ID_NOT_FOUND = 'resourceIdNotFound'
|
||||
RESOURCE_NOT_FOUND = 'resourceNotFound'
|
||||
RESPONSE_PREPARATION_FAILURE = 'responsePreparationFailure'
|
||||
REVISION_DELETION_NOT_SUPPORTED = 'revisionDeletionNotSupported'
|
||||
REVISION_NOT_FOUND = 'revisionNotFound'
|
||||
REVISIONS_NOT_SUPPORTED = 'revisionsNotSupported'
|
||||
SERVICE_LIMIT = 'serviceLimit'
|
||||
SERVICE_NOT_AVAILABLE = 'serviceNotAvailable'
|
||||
SHARE_IN_NOT_PERMITTED = 'shareInNotPermitted'
|
||||
SHARE_OUT_NOT_PERMITTED = 'shareOutNotPermitted'
|
||||
SHARE_OUT_NOT_PERMITTED_TO_USER = 'shareOutNotPermittedToUser'
|
||||
SHARE_OUT_WARNING = 'shareOutWarning'
|
||||
SHARING_RATE_LIMIT_EXCEEDED = 'sharingRateLimitExceeded'
|
||||
SHORTCUT_TARGET_INVALID = 'shortcutTargetInvalid'
|
||||
STORAGE_QUOTA_EXCEEDED = 'storageQuotaExceeded'
|
||||
SYSTEM_ERROR = 'systemError'
|
||||
TARGET_USER_ROLE_LIMITED_BY_LICENSE_RESTRICTION = 'targetUserRoleLimitedByLicenseRestriction'
|
||||
TEAMDRIVE_ALREADY_EXISTS = 'teamDriveAlreadyExists'
|
||||
TEAMDRIVE_DOMAIN_USERS_ONLY_RESTRICTION = 'teamDriveDomainUsersOnlyRestriction'
|
||||
TEAMDRIVE_TEAM_MEMBERS_ONLY_RESTRICTION = 'teamDriveTeamMembersOnlyRestriction'
|
||||
TEAMDRIVE_FILE_LIMIT_EXCEEDED = 'teamDriveFileLimitExceeded'
|
||||
TEAMDRIVE_HIERARCHY_TOO_DEEP = 'teamDriveHierarchyTooDeep'
|
||||
TEAMDRIVE_MEMBERSHIP_REQUIRED = 'teamDriveMembershipRequired'
|
||||
TEAMDRIVES_FOLDER_MOVE_IN_NOT_SUPPORTED = 'teamDrivesFolderMoveInNotSupported'
|
||||
TEAMDRIVES_FOLDER_SHARING_NOT_SUPPORTED = 'teamDrivesFolderSharingNotSupported'
|
||||
TEAMDRIVES_PARENT_LIMIT = 'teamDrivesParentLimit'
|
||||
TEAMDRIVES_SHARING_RESTRICTION_NOT_ALLOWED = 'teamDrivesSharingRestrictionNotAllowed'
|
||||
TEAMDRIVES_SHORTCUT_FILE_NOT_SUPPORTED = 'teamDrivesShortcutFileNotSupported'
|
||||
TIME_RANGE_EMPTY = 'timeRangeEmpty'
|
||||
TRANSIENT_ERROR = 'transientError'
|
||||
UNIMPLEMENTED_ERROR = 'unimplementedError'
|
||||
UNKNOWN_ERROR = 'unknownError'
|
||||
UNSUPPORTED_LANGUAGE_CODE = 'unsupportedLanguageCode'
|
||||
UNSUPPORTED_SUPERVISED_ACCOUNT = 'unsupportedSupervisedAccount'
|
||||
UPLOAD_TOO_LARGE = 'uploadTooLarge'
|
||||
USER_CANNOT_CREATE_TEAMDRIVES = 'userCannotCreateTeamDrives'
|
||||
USER_ACCESS = 'userAccess'
|
||||
USER_NOT_FOUND = 'userNotFound'
|
||||
USER_RATE_LIMIT_EXCEEDED = 'userRateLimitExceeded'
|
||||
#
|
||||
DEFAULT_RETRY_REASONS = [QUOTA_EXCEEDED, RATE_LIMIT_EXCEEDED, SHARING_RATE_LIMIT_EXCEEDED, USER_RATE_LIMIT_EXCEEDED,
|
||||
BACKEND_ERROR, BAD_GATEWAY, GATEWAY_TIMEOUT, INTERNAL_ERROR, TRANSIENT_ERROR]
|
||||
SERVICE_NOT_AVAILABLE_RETRY_REASONS = [SERVICE_NOT_AVAILABLE]
|
||||
ACTIVITY_THROW_REASONS = [SERVICE_NOT_AVAILABLE, BAD_REQUEST]
|
||||
ALERT_THROW_REASONS = [SERVICE_NOT_AVAILABLE, AUTH_ERROR, PERMISSION_DENIED]
|
||||
CALENDAR_THROW_REASONS = [SERVICE_NOT_AVAILABLE, AUTH_ERROR, NOT_A_CALENDAR_USER]
|
||||
CIGROUP_CREATE_THROW_REASONS = [SERVICE_NOT_AVAILABLE, ALREADY_EXISTS, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, INVALID, INVALID_ARGUMENT, PERMISSION_DENIED, FAILED_PRECONDITION]
|
||||
CIGROUP_GET_THROW_REASONS = [SERVICE_NOT_AVAILABLE, NOT_FOUND, GROUP_NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, BAD_REQUEST, INVALID, INVALID_ARGUMENT, SYSTEM_ERROR, PERMISSION_DENIED]
|
||||
CIGROUP_LIST_THROW_REASONS = [SERVICE_NOT_AVAILABLE, RESOURCE_NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, BAD_REQUEST, INVALID, INVALID_ARGUMENT, SYSTEM_ERROR, PERMISSION_DENIED]
|
||||
CIGROUP_LIST_USERKEY_THROW_REASONS = CIGROUP_LIST_THROW_REASONS+[INVALID_ARGUMENT]
|
||||
CIGROUP_UPDATE_THROW_REASONS = [SERVICE_NOT_AVAILABLE, NOT_FOUND, GROUP_NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS,
|
||||
FORBIDDEN, BAD_REQUEST, INVALID, INVALID_INPUT, INVALID_ARGUMENT,
|
||||
SYSTEM_ERROR, PERMISSION_DENIED, FAILED_PRECONDITION]
|
||||
CIGROUP_RETRY_REASONS = [INVALID, SYSTEM_ERROR, SERVICE_NOT_AVAILABLE]
|
||||
CIMEMBERS_THROW_REASONS = [SERVICE_NOT_AVAILABLE, MEMBER_NOT_FOUND, INVALID_MEMBER]
|
||||
CISSO_CREATE_THROW_REASONS = [SERVICE_NOT_AVAILABLE, FAILED_PRECONDITION, NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, INVALID, INVALID_ARGUMENT, PERMISSION_DENIED, INTERNAL_ERROR]
|
||||
CISSO_GET_THROW_REASONS = [SERVICE_NOT_AVAILABLE, NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, BAD_REQUEST, INVALID, SYSTEM_ERROR, PERMISSION_DENIED, INTERNAL_ERROR]
|
||||
CISSO_LIST_THROW_REASONS = [SERVICE_NOT_AVAILABLE, NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, BAD_REQUEST, INVALID, SYSTEM_ERROR, PERMISSION_DENIED, INTERNAL_ERROR]
|
||||
CISSO_UPDATE_THROW_REASONS = [SERVICE_NOT_AVAILABLE, NOT_FOUND, FAILED_PRECONDITION, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS,
|
||||
FORBIDDEN, BAD_REQUEST, INVALID, INVALID_INPUT, INVALID_ARGUMENT,
|
||||
SYSTEM_ERROR, PERMISSION_DENIED, INTERNAL_ERROR]
|
||||
CONTACT_DELEGATE_THROW_REASONS = [SERVICE_NOT_AVAILABLE, BAD_REQUEST, FAILED_PRECONDITION, PERMISSION_DENIED, FORBIDDEN, INVALID_ARGUMENT]
|
||||
COURSE_ACCESS_THROW_REASONS = [NOT_FOUND, INSUFFICIENT_PERMISSIONS, PERMISSION_DENIED, FORBIDDEN, INVALID_ARGUMENT]
|
||||
DRIVE_USER_THROW_REASONS = [SERVICE_NOT_AVAILABLE, AUTH_ERROR, DOMAIN_POLICY]
|
||||
DRIVE_ACCESS_THROW_REASONS = DRIVE_USER_THROW_REASONS+[FILE_NOT_FOUND, FORBIDDEN, INTERNAL_ERROR, INSUFFICIENT_FILE_PERMISSIONS, UNKNOWN_ERROR, INVALID]
|
||||
DRIVE_COPY_THROW_REASONS = DRIVE_ACCESS_THROW_REASONS+[CANNOT_COPY_FILE, BAD_REQUEST, RESPONSE_PREPARATION_FAILURE, TEAMDRIVES_SHARING_RESTRICTION_NOT_ALLOWED,
|
||||
FIELD_NOT_WRITABLE, RATE_LIMIT_EXCEEDED, USER_RATE_LIMIT_EXCEEDED,
|
||||
STORAGE_QUOTA_EXCEEDED, TEAMDRIVE_FILE_LIMIT_EXCEEDED, TEAMDRIVE_HIERARCHY_TOO_DEEP]
|
||||
DRIVE_GET_THROW_REASONS = DRIVE_USER_THROW_REASONS+[FILE_NOT_FOUND, DOWNLOAD_QUOTA_EXCEEDED]
|
||||
DRIVE3_CREATE_ACL_THROW_REASONS = [BAD_REQUEST, INVALID, INVALID_SHARING_REQUEST, OWNERSHIP_CHANGE_ACROSS_DOMAIN_NOT_PERMITTED,
|
||||
CANNOT_SET_EXPIRATION, CANNOT_SET_EXPIRATION_ON_ANYONE_OR_DOMAIN,
|
||||
EXPIRATION_DATES_MUST_BE_IN_THE_FUTURE, EXPIRATION_DATE_NOT_ALLOWED_FOR_SHARED_DRIVE_MEMBERS,
|
||||
NOT_FOUND, TEAMDRIVE_DOMAIN_USERS_ONLY_RESTRICTION, TEAMDRIVE_TEAM_MEMBERS_ONLY_RESTRICTION,
|
||||
TARGET_USER_ROLE_LIMITED_BY_LICENSE_RESTRICTION, INSUFFICIENT_ADMINISTRATOR_PRIVILEGES, SHARING_RATE_LIMIT_EXCEEDED,
|
||||
PUBLISH_OUT_NOT_PERMITTED, SHARE_IN_NOT_PERMITTED, SHARE_OUT_NOT_PERMITTED, SHARE_OUT_NOT_PERMITTED_TO_USER,
|
||||
CANNOT_SHARE_TEAMDRIVE_TOPFOLDER_WITH_ANYONEORDOMAINS,
|
||||
CANNOT_SHARE_TEAMDRIVE_WITH_NONGOOGLE_ACCOUNTS,
|
||||
OWNER_ON_TEAMDRIVE_ITEM_NOT_SUPPORTED,
|
||||
ORGANIZER_ON_NON_TEAMDRIVE_NOT_SUPPORTED,
|
||||
ORGANIZER_ON_NON_TEAMDRIVE_ITEM_NOT_SUPPORTED,
|
||||
FILE_ORGANIZER_NOT_YET_ENABLED_FOR_THIS_TEAMDRIVE,
|
||||
FILE_ORGANIZER_ON_FOLDERS_IN_SHARED_DRIVE_ONLY,
|
||||
FILE_ORGANIZER_ON_NON_TEAMDRIVE_NOT_SUPPORTED,
|
||||
CANNOT_MODIFY_INHERITED_PERMISSION,
|
||||
TEAMDRIVES_FOLDER_SHARING_NOT_SUPPORTED, INVALID_LINK_VISIBILITY, ABUSIVE_CONTENT_RESTRICTION]
|
||||
DRIVE3_GET_ACL_REASONS = DRIVE_USER_THROW_REASONS+[FILE_NOT_FOUND, FORBIDDEN, INTERNAL_ERROR,
|
||||
INSUFFICIENT_ADMINISTRATOR_PRIVILEGES, INSUFFICIENT_FILE_PERMISSIONS,
|
||||
UNKNOWN_ERROR, INVALID]
|
||||
DRIVE3_UPDATE_ACL_THROW_REASONS = [BAD_REQUEST, INVALID_OWNERSHIP_TRANSFER, CANNOT_REMOVE_OWNER,
|
||||
CANNOT_SET_EXPIRATION, CANNOT_SET_EXPIRATION_ON_ANYONE_OR_DOMAIN,
|
||||
EXPIRATION_DATES_MUST_BE_IN_THE_FUTURE, EXPIRATION_DATE_NOT_ALLOWED_FOR_SHARED_DRIVE_MEMBERS,
|
||||
OWNERSHIP_CHANGE_ACROSS_DOMAIN_NOT_PERMITTED,
|
||||
NOT_FOUND, TEAMDRIVE_DOMAIN_USERS_ONLY_RESTRICTION, TEAMDRIVE_TEAM_MEMBERS_ONLY_RESTRICTION,
|
||||
TARGET_USER_ROLE_LIMITED_BY_LICENSE_RESTRICTION, INSUFFICIENT_ADMINISTRATOR_PRIVILEGES, SHARING_RATE_LIMIT_EXCEEDED,
|
||||
PUBLISH_OUT_NOT_PERMITTED, SHARE_IN_NOT_PERMITTED, SHARE_OUT_NOT_PERMITTED, SHARE_OUT_NOT_PERMITTED_TO_USER,
|
||||
CANNOT_SHARE_TEAMDRIVE_TOPFOLDER_WITH_ANYONEORDOMAINS,
|
||||
CANNOT_SHARE_TEAMDRIVE_WITH_NONGOOGLE_ACCOUNTS,
|
||||
OWNER_ON_TEAMDRIVE_ITEM_NOT_SUPPORTED,
|
||||
ORGANIZER_ON_NON_TEAMDRIVE_NOT_SUPPORTED,
|
||||
ORGANIZER_ON_NON_TEAMDRIVE_ITEM_NOT_SUPPORTED,
|
||||
FILE_ORGANIZER_NOT_YET_ENABLED_FOR_THIS_TEAMDRIVE,
|
||||
FILE_ORGANIZER_ON_FOLDERS_IN_SHARED_DRIVE_ONLY,
|
||||
FILE_ORGANIZER_ON_NON_TEAMDRIVE_NOT_SUPPORTED,
|
||||
CANNOT_UPDATE_PERMISSION,
|
||||
CANNOT_MODIFY_INHERITED_TEAMDRIVE_PERMISSION, CANNOT_MODIFY_INHERITED_PERMISSION,
|
||||
FIELD_NOT_WRITABLE, PERMISSION_NOT_FOUND]
|
||||
DRIVE3_DELETE_ACL_THROW_REASONS = [BAD_REQUEST, NOT_FOUND, PERMISSION_NOT_FOUND, CANNOT_REMOVE_OWNER,
|
||||
CANNOT_MODIFY_INHERITED_TEAMDRIVE_PERMISSION, CANNOT_MODIFY_INHERITED_PERMISSION,
|
||||
INSUFFICIENT_ADMINISTRATOR_PRIVILEGES, SHARING_RATE_LIMIT_EXCEEDED,
|
||||
CANNOT_DELETE_PERMISSION, FILE_NEVER_WRITABLE]
|
||||
DRIVE3_MODIFY_LABEL_THROW_REASONS = DRIVE_USER_THROW_REASONS+[FILE_NOT_FOUND, NOT_FOUND, FORBIDDEN, INTERNAL_ERROR,
|
||||
FILE_NEVER_WRITABLE, APPLY_LABEL_FORBIDDEN,
|
||||
INSUFFICIENT_ADMINISTRATOR_PRIVILEGES, INSUFFICIENT_FILE_PERMISSIONS,
|
||||
UNKNOWN_ERROR, INVALID_INPUT, BAD_REQUEST,
|
||||
LABEL_MULTIPLE_VALUES_FOR_SINGULAR_FIELD, LABEL_MUTATION_FORBIDDEN,
|
||||
LABEL_MUTATION_ILLEGAL_SELECTION, LABEL_MUTATION_UNKNOWN_FIELD]
|
||||
DOCS_ACCESS_THROW_REASONS = DRIVE_USER_THROW_REASONS+[NOT_FOUND, PERMISSION_DENIED, FORBIDDEN, INTERNAL_ERROR, INSUFFICIENT_FILE_PERMISSIONS,
|
||||
BAD_REQUEST, INVALID, INVALID_ARGUMENT, FAILED_PRECONDITION]
|
||||
GMAIL_THROW_REASONS = [SERVICE_NOT_AVAILABLE, BAD_REQUEST]
|
||||
GMAIL_LIST_THROW_REASONS = [FAILED_PRECONDITION, PERMISSION_DENIED, INVALID, INVALID_ARGUMENT]
|
||||
GMAIL_SMIME_THROW_REASONS = [SERVICE_NOT_AVAILABLE, BAD_REQUEST, INVALID_ARGUMENT, FORBIDDEN, NOT_FOUND, PERMISSION_DENIED]
|
||||
GROUP_GET_THROW_REASONS = [GROUP_NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, BAD_REQUEST, INVALID, SYSTEM_ERROR]
|
||||
GROUP_GET_RETRY_REASONS = [INVALID, SYSTEM_ERROR, SERVICE_NOT_AVAILABLE]
|
||||
GROUP_CREATE_THROW_REASONS = [DUPLICATE, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, INVALID, INVALID_INPUT, RESOURCE_NOT_FOUND]
|
||||
GROUP_UPDATE_THROW_REASONS = [GROUP_NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, INVALID, INVALID_INPUT]
|
||||
GROUP_SETTINGS_THROW_REASONS = [NOT_FOUND, GROUP_NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, SYSTEM_ERROR, PERMISSION_DENIED,
|
||||
INVALID, INVALID_ARGUMENT, INVALID_PARAMETER, INVALID_ATTRIBUTE_VALUE, INVALID_INPUT,
|
||||
SERVICE_LIMIT, SERVICE_NOT_AVAILABLE, AUTH_ERROR, REQUIRED]
|
||||
GROUP_SETTINGS_RETRY_REASONS = [INVALID, SERVICE_LIMIT, SERVICE_NOT_AVAILABLE]
|
||||
GROUP_LIST_THROW_REASONS = [RESOURCE_NOT_FOUND, DOMAIN_NOT_FOUND, FORBIDDEN, BAD_REQUEST, PERMISSION_DENIED]
|
||||
GROUP_LIST_USERKEY_THROW_REASONS = GROUP_LIST_THROW_REASONS+[INVALID_MEMBER, INVALID_INPUT]
|
||||
KEEP_THROW_REASONS = [AUTH_ERROR, BAD_REQUEST, PERMISSION_DENIED, INVALID_ARGUMENT, NOT_FOUND]
|
||||
LOOKERSTUDIO_THROW_REASONS = [INVALID_ARGUMENT, SERVICE_NOT_AVAILABLE, BAD_REQUEST, NOT_FOUND, PERMISSION_DENIED, INTERNAL_ERROR]
|
||||
MEMBERS_THROW_REASONS = [GROUP_NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, INVALID, FORBIDDEN, SERVICE_NOT_AVAILABLE, PERMISSION_DENIED]
|
||||
MEMBERS_RETRY_REASONS = [SYSTEM_ERROR, SERVICE_NOT_AVAILABLE]
|
||||
ORGUNIT_GET_THROW_REASONS = [INVALID_ORGUNIT, ORGUNIT_NOT_FOUND, BACKEND_ERROR, BAD_REQUEST, INVALID_CUSTOMER_ID, LOGIN_REQUIRED]
|
||||
PEOPLE_ACCESS_THROW_REASONS = [SERVICE_NOT_AVAILABLE, FORBIDDEN, PERMISSION_DENIED, FAILED_PRECONDITION]
|
||||
RESELLER_THROW_REASONS = [BAD_REQUEST, RESOURCE_NOT_FOUND, FORBIDDEN, INVALID]
|
||||
SHEETS_ACCESS_THROW_REASONS = DRIVE_USER_THROW_REASONS+[NOT_FOUND, PERMISSION_DENIED, FORBIDDEN, INTERNAL_ERROR, INSUFFICIENT_FILE_PERMISSIONS,
|
||||
BAD_REQUEST, INVALID, INVALID_ARGUMENT, FAILED_PRECONDITION]
|
||||
TAGMANAGER_THROW_REASONS = [BAD_REQUEST, PERMISSION_DENIED, INVALID, NOT_FOUND, ACCESS_NOT_CONFIGURED]
|
||||
TASK_THROW_REASONS = [BAD_REQUEST, PERMISSION_DENIED, INVALID, NOT_FOUND, ACCESS_NOT_CONFIGURED]
|
||||
TASKLIST_THROW_REASONS = [BAD_REQUEST, PERMISSION_DENIED, INVALID, NOT_FOUND, ACCESS_NOT_CONFIGURED]
|
||||
USER_GET_THROW_REASONS = [USER_NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, BAD_REQUEST, SYSTEM_ERROR]
|
||||
YOUTUBE_THROW_REASONS = [SERVICE_NOT_AVAILABLE, AUTH_ERROR, UNSUPPORTED_SUPERVISED_ACCOUNT, UNSUPPORTED_LANGUAGE_CODE, CONTENT_OWNER_ACCOUNT_NOT_FOUND]
|
||||
|
||||
REASON_MESSAGE_MAP = {
|
||||
ABORTED: [
|
||||
('Label name exists or conflicts', DUPLICATE),
|
||||
('The operation was aborted', ABORTED),
|
||||
],
|
||||
CONDITION_NOT_MET: [
|
||||
('Cyclic memberships not allowed', CYCLIC_MEMBERSHIPS_NOT_ALLOWED),
|
||||
('undelete', DELETED_USER_NOT_FOUND),
|
||||
],
|
||||
FAILED_PRECONDITION: [
|
||||
('Bad Request', BAD_REQUEST),
|
||||
('Mail service not enabled', SERVICE_NOT_AVAILABLE),
|
||||
],
|
||||
INVALID: [
|
||||
('userId', USER_NOT_FOUND),
|
||||
('memberKey', INVALID_MEMBER),
|
||||
('A system error has occurred', SYSTEM_ERROR),
|
||||
('Expiration dates must be in the future', EXPIRATION_DATES_MUST_BE_IN_THE_FUTURE),
|
||||
('Invalid attribute value', INVALID_ATTRIBUTE_VALUE),
|
||||
('Invalid Customer Id', INVALID_CUSTOMER_ID),
|
||||
('Invalid Input: INVALID_OU_ID', INVALID_ORGUNIT),
|
||||
('Invalid Input: custom_schema', INVALID_SCHEMA_VALUE),
|
||||
('Invalid Input: groupKey', INVALID_INPUT),
|
||||
('Invalid Input: resource', INVALID_RESOURCE),
|
||||
('Invalid Input:', INVALID_INPUT),
|
||||
('Invalid Input', INVALID_INPUT),
|
||||
('Invalid Org Unit', INVALID_ORGUNIT),
|
||||
('Invalid Ou Id', INVALID_ORGUNIT),
|
||||
('Invalid Ou Name', INVALID_ORGUNIT_NAME),
|
||||
('Invalid Parent Orgunit Id', INVALID_PARENT_ORGUNIT),
|
||||
('Invalid query', INVALID_QUERY),
|
||||
('Invalid scope value', INVALID_SCOPE_VALUE),
|
||||
('Invalid value', INVALID_INPUT),
|
||||
('New domain name is not a verified secondary domain', DOMAIN_NOT_VERIFIED_SECONDARY),
|
||||
('PermissionDenied', PERMISSION_DENIED),
|
||||
],
|
||||
INVALID_ARGUMENT: [
|
||||
('Cannot delete primary send-as', CANNOT_DELETE_PRIMARY_SENDAS),
|
||||
('Invalid id value', INVALID_MESSAGE_ID),
|
||||
('Invalid ids value', INVALID_MESSAGE_ID),
|
||||
],
|
||||
NOT_FOUND: [
|
||||
('userKey', USER_NOT_FOUND),
|
||||
('groupKey', GROUP_NOT_FOUND),
|
||||
('memberKey', MEMBER_NOT_FOUND),
|
||||
('photo', PHOTO_NOT_FOUND),
|
||||
('resource_id', RESOURCE_ID_NOT_FOUND),
|
||||
('resourceId', RESOURCE_ID_NOT_FOUND),
|
||||
('Customer doesn\'t exist', CUSTOMER_NOT_FOUND),
|
||||
('Domain alias does not exist', DOMAIN_ALIAS_NOT_FOUND),
|
||||
('Domain not found', DOMAIN_NOT_FOUND),
|
||||
('domain', DOMAIN_NOT_FOUND),
|
||||
('File not found', FILE_NOT_FOUND),
|
||||
('Org unit not found', ORGUNIT_NOT_FOUND),
|
||||
('Permission not found', PERMISSION_NOT_FOUND),
|
||||
('Resource Not Found', RESOURCE_NOT_FOUND),
|
||||
('Revision not found', REVISION_NOT_FOUND),
|
||||
('Shared Drive not found', NOT_FOUND),
|
||||
('Not Found', NOT_FOUND),
|
||||
],
|
||||
REQUIRED: [
|
||||
('Login Required', LOGIN_REQUIRED),
|
||||
('memberKey', MEMBER_NOT_FOUND),
|
||||
],
|
||||
RESOURCE_NOT_FOUND: [
|
||||
('resourceId', RESOURCE_ID_NOT_FOUND),
|
||||
],
|
||||
}
|
||||
|
||||
class aborted(Exception):
|
||||
pass
|
||||
class abusiveContentRestriction(Exception):
|
||||
pass
|
||||
class accessNotConfigured(Exception):
|
||||
pass
|
||||
class adminCannotUnsuspend(Exception):
|
||||
pass
|
||||
class alreadyExists(Exception):
|
||||
pass
|
||||
class applyLabelForbidden(Exception):
|
||||
pass
|
||||
class authError(Exception):
|
||||
pass
|
||||
class backendError(Exception):
|
||||
pass
|
||||
class badRequest(Exception):
|
||||
pass
|
||||
class cannotAddParent(Exception):
|
||||
pass
|
||||
class cannotChangeOrganizer(Exception):
|
||||
pass
|
||||
class cannotChangeOrganizerOfInstance(Exception):
|
||||
pass
|
||||
class cannotChangeOwnAcl(Exception):
|
||||
pass
|
||||
class cannotChangeOwnerAcl(Exception):
|
||||
pass
|
||||
class cannotChangeOwnPrimarySubscription(Exception):
|
||||
pass
|
||||
class cannotCopyFile(Exception):
|
||||
pass
|
||||
class cannotDeleteOnlyRevision(Exception):
|
||||
pass
|
||||
class cannotDeletePermission(Exception):
|
||||
pass
|
||||
class cannotDeletePrimaryCalendar(Exception):
|
||||
pass
|
||||
class cannotDeletePrimarySendAs(Exception):
|
||||
pass
|
||||
class cannotDeleteResourceWithChildren(Exception):
|
||||
pass
|
||||
class cannotModifyAclOfCalendarOwner(Exception):
|
||||
pass
|
||||
class cannotModifyInheritedPermission(Exception):
|
||||
pass
|
||||
class cannotModifyInheritedTeamDrivePermission(Exception):
|
||||
pass
|
||||
class cannotModifyRestrictedLabel(Exception):
|
||||
pass
|
||||
class cannotModifyViewersCanCopyContent(Exception):
|
||||
pass
|
||||
class cannotMoveTrashedItemIntoTeamDrive(Exception):
|
||||
pass
|
||||
class cannotMoveTrashedItemOutOfTeamDrive(Exception):
|
||||
pass
|
||||
class cannotRemoveOwner(Exception):
|
||||
pass
|
||||
class cannotSetExpiration(Exception):
|
||||
pass
|
||||
class cannotSetExpirationOnAnyoneOrDomain(Exception):
|
||||
pass
|
||||
class cannotShareGroupsWithLink(Exception):
|
||||
pass
|
||||
class cannotShareUsersWithLink(Exception):
|
||||
pass
|
||||
class cannotShareTeamDriveTopFolderWithAnyoneOrDomains(Exception):
|
||||
pass
|
||||
class cannotShareTeamDriveWithNonGoogleAccounts(Exception):
|
||||
pass
|
||||
class cannotUnsubscribeFromOwnedCalendar(Exception):
|
||||
pass
|
||||
class cannotUpdatePermission(Exception):
|
||||
pass
|
||||
class conditionNotMet(Exception):
|
||||
pass
|
||||
class conflict(Exception):
|
||||
pass
|
||||
class contentOwnerAccountNotFound(Exception):
|
||||
pass
|
||||
class crossDomainMoveRestriction(Exception):
|
||||
pass
|
||||
class customerExceededRoleAssignmentsLimit(Exception):
|
||||
pass
|
||||
class customerNotFound(Exception):
|
||||
pass
|
||||
class cyclicMembershipsNotAllowed(Exception):
|
||||
pass
|
||||
class deleted(Exception):
|
||||
pass
|
||||
class deletedUserNotFound(Exception):
|
||||
pass
|
||||
class domainAliasNotFound(Exception):
|
||||
pass
|
||||
class domainCannotUseApis(Exception):
|
||||
pass
|
||||
class domainNotFound(Exception):
|
||||
pass
|
||||
class domainNotVerifiedSecondary(Exception):
|
||||
pass
|
||||
class domainPolicy(Exception):
|
||||
pass
|
||||
class downloadQuotaExceeded(Exception):
|
||||
pass
|
||||
class duplicate(Exception):
|
||||
pass
|
||||
class eventDurationExceedsLimit(Exception):
|
||||
pass
|
||||
class eventTypeRestriction(Exception):
|
||||
pass
|
||||
class expirationDatesMustBeInTheFuture(Exception):
|
||||
pass
|
||||
class expirationDateNotAllowedForSharedDriveMembers(Exception):
|
||||
pass
|
||||
class failedPrecondition(Exception):
|
||||
pass
|
||||
class fieldInUse(Exception):
|
||||
pass
|
||||
class fieldNotWritable(Exception):
|
||||
pass
|
||||
class fileNeverWritable(Exception):
|
||||
pass
|
||||
class fileNotFound(Exception):
|
||||
pass
|
||||
class fileOrganizerNotYetEnabledForThisTeamDrive(Exception):
|
||||
pass
|
||||
class fileOrganizerOnFoldersInSharedDriveOnly(Exception):
|
||||
pass
|
||||
class fileOrganizerOnNonTeamDriveNotSupported(Exception):
|
||||
pass
|
||||
class fileOwnerNotMemberOfTeamDrive(Exception):
|
||||
pass
|
||||
class fileOwnerNotMemberOfWriterDomain(Exception):
|
||||
pass
|
||||
class fileWriterTeamDriveMoveInDisabled(Exception):
|
||||
pass
|
||||
class forbidden(Exception):
|
||||
pass
|
||||
class groupNotFound(Exception):
|
||||
pass
|
||||
class illegalAccessRoleForDefault(Exception):
|
||||
pass
|
||||
class insufficientAdministratorPrivileges(Exception):
|
||||
pass
|
||||
class insufficientArchivedUserLicenses(Exception):
|
||||
pass
|
||||
class insufficientFilePermissions(Exception):
|
||||
pass
|
||||
class insufficientParentPermissions(Exception):
|
||||
pass
|
||||
class insufficientPermissions(Exception):
|
||||
pass
|
||||
class internalError(Exception):
|
||||
pass
|
||||
class invalid(Exception):
|
||||
pass
|
||||
class invalidArgument(Exception):
|
||||
pass
|
||||
class invalidAttributeValue(Exception):
|
||||
pass
|
||||
class invalidCustomerId(Exception):
|
||||
pass
|
||||
class invalidInput(Exception):
|
||||
pass
|
||||
class invalidLinkVisibility(Exception):
|
||||
pass
|
||||
class invalidMember(Exception):
|
||||
pass
|
||||
class invalidMessageId(Exception):
|
||||
pass
|
||||
class invalidOrgunit(Exception):
|
||||
pass
|
||||
class invalidOrgunitName(Exception):
|
||||
pass
|
||||
class invalidOwnershipTransfer(Exception):
|
||||
pass
|
||||
class invalidParameter(Exception):
|
||||
pass
|
||||
class invalidParentOrgunit(Exception):
|
||||
pass
|
||||
class invalidQuery(Exception):
|
||||
pass
|
||||
class invalidResource(Exception):
|
||||
pass
|
||||
class invalidSchemaValue(Exception):
|
||||
pass
|
||||
class invalidScopeValue(Exception):
|
||||
pass
|
||||
class invalidSharingRequest(Exception):
|
||||
pass
|
||||
class labelMultipleValuesForSingularField(Exception):
|
||||
pass
|
||||
class labelMutationForbidden(Exception):
|
||||
pass
|
||||
class labelMutationIllegalSelection(Exception):
|
||||
pass
|
||||
class labelMutationUnknownField(Exception):
|
||||
pass
|
||||
class limitExceeded(Exception):
|
||||
pass
|
||||
class loginRequired(Exception):
|
||||
pass
|
||||
class malformedWorkingLocationEvent(Exception):
|
||||
pass
|
||||
class memberNotFound(Exception):
|
||||
pass
|
||||
class noListTeamDrivesAdministratorPrivilege(Exception):
|
||||
pass
|
||||
class noManageTeamDriveAdministratorPrivilege(Exception):
|
||||
pass
|
||||
class notACalendarUser(Exception):
|
||||
pass
|
||||
class notFound(Exception):
|
||||
pass
|
||||
class notImplemented(Exception):
|
||||
pass
|
||||
class operationNotSupported(Exception):
|
||||
pass
|
||||
class organizerOnNonTeamDriveNotSupported(Exception):
|
||||
pass
|
||||
class organizerOnNonTeamDriveItemNotSupported(Exception):
|
||||
pass
|
||||
class orgunitNotFound(Exception):
|
||||
pass
|
||||
class outsideDomainMemberCannotChangeTeamDriveRestrictions(Exception):
|
||||
pass
|
||||
class ownerOnTeamDriveItemNotSupported(Exception):
|
||||
pass
|
||||
class ownershipChangeAcrossDomainNotPermitted(Exception):
|
||||
pass
|
||||
class participantIsNeitherOrganizerNorAttendee(Exception):
|
||||
pass
|
||||
class permissionDenied(Exception):
|
||||
pass
|
||||
class permissionNotFound(Exception):
|
||||
pass
|
||||
class photoNotFound(Exception):
|
||||
pass
|
||||
class publishOutNotPermitted(Exception):
|
||||
pass
|
||||
class queryRequiresAdminCredentials(Exception):
|
||||
pass
|
||||
class quotaExceeded(Exception):
|
||||
pass
|
||||
class rateLimitExceeded(Exception):
|
||||
pass
|
||||
class required(Exception):
|
||||
pass
|
||||
class requiredAccessLevel(Exception):
|
||||
pass
|
||||
class resourceExhausted(Exception):
|
||||
pass
|
||||
class resourceIdNotFound(Exception):
|
||||
pass
|
||||
class resourceNotFound(Exception):
|
||||
pass
|
||||
class responsePreparationFailure(Exception):
|
||||
pass
|
||||
class revisionDeletionNotSupported(Exception):
|
||||
pass
|
||||
class revisionNotFound(Exception):
|
||||
pass
|
||||
class revisionsNotSupported(Exception):
|
||||
pass
|
||||
class serviceLimit(Exception):
|
||||
pass
|
||||
class serviceNotAvailable(Exception):
|
||||
pass
|
||||
class shareInNotPermitted(Exception):
|
||||
pass
|
||||
class shareOutNotPermitted(Exception):
|
||||
pass
|
||||
class shareOutNotPermittedToUser(Exception):
|
||||
pass
|
||||
class shareOutWarning(Exception):
|
||||
pass
|
||||
class sharingRateLimitExceeded(Exception):
|
||||
pass
|
||||
class shortcutTargetInvalid(Exception):
|
||||
pass
|
||||
class storageQuotaExceeded(Exception):
|
||||
pass
|
||||
class systemError(Exception):
|
||||
pass
|
||||
class targetUserRoleLimitedByLicenseRestriction(Exception):
|
||||
pass
|
||||
class teamDriveAlreadyExists(Exception):
|
||||
pass
|
||||
class teamDriveDomainUsersOnlyRestriction(Exception):
|
||||
pass
|
||||
class teamDriveTeamMembersOnlyRestriction(Exception):
|
||||
pass
|
||||
class teamDriveFileLimitExceeded(Exception):
|
||||
pass
|
||||
class teamDriveHierarchyTooDeep(Exception):
|
||||
pass
|
||||
class teamDriveMembershipRequired(Exception):
|
||||
pass
|
||||
class teamDrivesFolderMoveInNotSupported(Exception):
|
||||
pass
|
||||
class teamDrivesFolderSharingNotSupported(Exception):
|
||||
pass
|
||||
class teamDrivesParentLimit(Exception):
|
||||
pass
|
||||
class teamDrivesSharingRestrictionNotAllowed(Exception):
|
||||
pass
|
||||
class teamDrivesShortcutFileNotSupported(Exception):
|
||||
pass
|
||||
class timeRangeEmpty(Exception):
|
||||
pass
|
||||
class transientError(Exception):
|
||||
pass
|
||||
class unimplementedError(Exception):
|
||||
pass
|
||||
class unknownError(Exception):
|
||||
pass
|
||||
class unsupportedLanguageCode(Exception):
|
||||
pass
|
||||
class unsupportedSupervisedAccount(Exception):
|
||||
pass
|
||||
class uploadTooLarge(Exception):
|
||||
pass
|
||||
class userCannotCreateTeamDrives(Exception):
|
||||
pass
|
||||
class userAccess(Exception):
|
||||
pass
|
||||
class userNotFound(Exception):
|
||||
pass
|
||||
class userRateLimitExceeded(Exception):
|
||||
pass
|
||||
|
||||
REASON_EXCEPTION_MAP = {
|
||||
ABORTED: aborted,
|
||||
ABUSIVE_CONTENT_RESTRICTION: abusiveContentRestriction,
|
||||
ACCESS_NOT_CONFIGURED: accessNotConfigured,
|
||||
ADMIN_CANNOT_UNSUSPEND: adminCannotUnsuspend,
|
||||
ALREADY_EXISTS: alreadyExists,
|
||||
APPLY_LABEL_FORBIDDEN: applyLabelForbidden,
|
||||
AUTH_ERROR: authError,
|
||||
BACKEND_ERROR: backendError,
|
||||
BAD_REQUEST: badRequest,
|
||||
CANNOT_ADD_PARENT: cannotAddParent,
|
||||
CANNOT_CHANGE_ORGANIZER: cannotChangeOrganizer,
|
||||
CANNOT_CHANGE_ORGANIZER_OF_INSTANCE: cannotChangeOrganizerOfInstance,
|
||||
CANNOT_CHANGE_OWN_ACL: cannotChangeOwnAcl,
|
||||
CANNOT_CHANGE_OWNER_ACL: cannotChangeOwnerAcl,
|
||||
CANNOT_CHANGE_OWN_PRIMARY_SUBSCRIPTION: cannotChangeOwnPrimarySubscription,
|
||||
CANNOT_COPY_FILE: cannotCopyFile,
|
||||
CANNOT_DELETE_ONLY_REVISION: cannotDeleteOnlyRevision,
|
||||
CANNOT_DELETE_PERMISSION: cannotDeletePermission,
|
||||
CANNOT_DELETE_PRIMARY_CALENDAR: cannotDeletePrimaryCalendar,
|
||||
CANNOT_DELETE_PRIMARY_SENDAS: cannotDeletePrimarySendAs,
|
||||
CANNOT_DELETE_RESOURCE_WITH_CHILDREN: cannotDeleteResourceWithChildren,
|
||||
CANNOT_MODIFY_ACL_OF_CALENDAR_OWNER: cannotModifyAclOfCalendarOwner,
|
||||
CANNOT_MODIFY_INHERITED_PERMISSION: cannotModifyInheritedPermission,
|
||||
CANNOT_MODIFY_INHERITED_TEAMDRIVE_PERMISSION: cannotModifyInheritedTeamDrivePermission,
|
||||
CANNOT_MODIFY_RESTRICTED_LABEL: cannotModifyRestrictedLabel,
|
||||
CANNOT_MODIFY_VIEWERS_CAN_COPY_CONTENT: cannotModifyViewersCanCopyContent,
|
||||
CANNOT_MOVE_TRASHED_ITEM_INTO_TEAMDRIVE: cannotMoveTrashedItemIntoTeamDrive,
|
||||
CANNOT_MOVE_TRASHED_ITEM_OUT_OF_TEAMDRIVE: cannotMoveTrashedItemOutOfTeamDrive,
|
||||
CANNOT_REMOVE_OWNER: cannotRemoveOwner,
|
||||
CANNOT_SET_EXPIRATION: cannotSetExpiration,
|
||||
CANNOT_SET_EXPIRATION_ON_ANYONE_OR_DOMAIN: cannotSetExpirationOnAnyoneOrDomain,
|
||||
CANNOT_SHARE_GROUPS_WITHLINK: cannotShareGroupsWithLink,
|
||||
CANNOT_SHARE_USERS_WITHLINK: cannotShareUsersWithLink,
|
||||
CANNOT_SHARE_TEAMDRIVE_TOPFOLDER_WITH_ANYONEORDOMAINS: cannotShareTeamDriveTopFolderWithAnyoneOrDomains,
|
||||
CANNOT_SHARE_TEAMDRIVE_WITH_NONGOOGLE_ACCOUNTS: cannotShareTeamDriveWithNonGoogleAccounts,
|
||||
CANNOT_UNSUBSCRIBE_FROM_OWNED_CALENDAR: cannotUnsubscribeFromOwnedCalendar,
|
||||
CANNOT_UPDATE_PERMISSION: cannotUpdatePermission,
|
||||
CONDITION_NOT_MET: conditionNotMet,
|
||||
CONFLICT: conflict,
|
||||
CONTENT_OWNER_ACCOUNT_NOT_FOUND: contentOwnerAccountNotFound,
|
||||
CROSS_DOMAIN_MOVE_RESTRICTION: crossDomainMoveRestriction,
|
||||
CUSTOMER_EXCEEDED_ROLE_ASSIGNMENTS_LIMIT: customerExceededRoleAssignmentsLimit,
|
||||
CUSTOMER_NOT_FOUND: customerNotFound,
|
||||
CYCLIC_MEMBERSHIPS_NOT_ALLOWED: cyclicMembershipsNotAllowed,
|
||||
DELETED: deleted,
|
||||
DELETED_USER_NOT_FOUND: deletedUserNotFound,
|
||||
DOMAIN_ALIAS_NOT_FOUND: domainAliasNotFound,
|
||||
DOMAIN_CANNOT_USE_APIS: domainCannotUseApis,
|
||||
DOMAIN_NOT_FOUND: domainNotFound,
|
||||
DOMAIN_NOT_VERIFIED_SECONDARY: domainNotVerifiedSecondary,
|
||||
DOMAIN_POLICY: domainPolicy,
|
||||
DOWNLOAD_QUOTA_EXCEEDED: downloadQuotaExceeded,
|
||||
DUPLICATE: duplicate,
|
||||
EVENT_DURATION_EXCEEDS_LIMIT: eventDurationExceedsLimit,
|
||||
EVENT_TYPE_RESTRICTION: eventTypeRestriction,
|
||||
EXPIRATION_DATES_MUST_BE_IN_THE_FUTURE: expirationDatesMustBeInTheFuture,
|
||||
EXPIRATION_DATE_NOT_ALLOWED_FOR_SHARED_DRIVE_MEMBERS: expirationDateNotAllowedForSharedDriveMembers,
|
||||
FAILED_PRECONDITION: failedPrecondition,
|
||||
FIELD_IN_USE: fieldInUse,
|
||||
FIELD_NOT_WRITABLE: fieldNotWritable,
|
||||
FILE_NEVER_WRITABLE: fileNeverWritable,
|
||||
FILE_NOT_FOUND: fileNotFound,
|
||||
FILE_ORGANIZER_NOT_YET_ENABLED_FOR_THIS_TEAMDRIVE: fileOrganizerNotYetEnabledForThisTeamDrive,
|
||||
FILE_ORGANIZER_ON_FOLDERS_IN_SHARED_DRIVE_ONLY: fileOrganizerOnFoldersInSharedDriveOnly,
|
||||
FILE_ORGANIZER_ON_NON_TEAMDRIVE_NOT_SUPPORTED: fileOrganizerOnNonTeamDriveNotSupported,
|
||||
FILE_OWNER_NOT_MEMBER_OF_TEAMDRIVE: fileOwnerNotMemberOfTeamDrive,
|
||||
FILE_OWNER_NOT_MEMBER_OF_WRITER_DOMAIN: fileOwnerNotMemberOfWriterDomain,
|
||||
FILE_WRITER_TEAMDRIVE_MOVE_IN_DISABLED: fileWriterTeamDriveMoveInDisabled,
|
||||
FORBIDDEN: forbidden,
|
||||
GROUP_NOT_FOUND: groupNotFound,
|
||||
ILLEGAL_ACCESS_ROLE_FOR_DEFAULT: illegalAccessRoleForDefault,
|
||||
INSUFFICIENT_ADMINISTRATOR_PRIVILEGES: insufficientAdministratorPrivileges,
|
||||
INSUFFICIENT_ARCHIVED_USER_LICENSES: insufficientArchivedUserLicenses,
|
||||
INSUFFICIENT_FILE_PERMISSIONS: insufficientFilePermissions,
|
||||
INSUFFICIENT_PARENT_PERMISSIONS: insufficientParentPermissions,
|
||||
INSUFFICIENT_PERMISSIONS: insufficientPermissions,
|
||||
INTERNAL_ERROR: internalError,
|
||||
INVALID: invalid,
|
||||
INVALID_ARGUMENT: invalidArgument,
|
||||
INVALID_ATTRIBUTE_VALUE: invalidAttributeValue,
|
||||
INVALID_CUSTOMER_ID: invalidCustomerId,
|
||||
INVALID_INPUT: invalidInput,
|
||||
INVALID_LINK_VISIBILITY: invalidLinkVisibility,
|
||||
INVALID_MEMBER: invalidMember,
|
||||
INVALID_MESSAGE_ID: invalidMessageId,
|
||||
INVALID_ORGUNIT: invalidOrgunit,
|
||||
INVALID_ORGUNIT_NAME: invalidOrgunitName,
|
||||
INVALID_OWNERSHIP_TRANSFER: invalidOwnershipTransfer,
|
||||
INVALID_PARAMETER: invalidParameter,
|
||||
INVALID_PARENT_ORGUNIT: invalidParentOrgunit,
|
||||
INVALID_QUERY: invalidQuery,
|
||||
INVALID_RESOURCE: invalidResource,
|
||||
INVALID_SCHEMA_VALUE: invalidSchemaValue,
|
||||
INVALID_SCOPE_VALUE: invalidScopeValue,
|
||||
INVALID_SHARING_REQUEST: invalidSharingRequest,
|
||||
LABEL_MULTIPLE_VALUES_FOR_SINGULAR_FIELD: labelMultipleValuesForSingularField,
|
||||
LABEL_MUTATION_FORBIDDEN: labelMutationForbidden,
|
||||
LABEL_MUTATION_ILLEGAL_SELECTION: labelMutationIllegalSelection,
|
||||
LABEL_MUTATION_UNKNOWN_FIELD: labelMutationUnknownField,
|
||||
LIMIT_EXCEEDED: limitExceeded,
|
||||
LOGIN_REQUIRED: loginRequired,
|
||||
MALFORMED_WORKING_LOCATION_EVENT: malformedWorkingLocationEvent,
|
||||
MEMBER_NOT_FOUND: memberNotFound,
|
||||
NO_LIST_TEAMDRIVES_ADMINISTRATOR_PRIVILEGE: noListTeamDrivesAdministratorPrivilege,
|
||||
NO_MANAGE_TEAMDRIVE_ADMINISTRATOR_PRIVILEGE: noManageTeamDriveAdministratorPrivilege,
|
||||
NOT_A_CALENDAR_USER: notACalendarUser,
|
||||
NOT_FOUND: notFound,
|
||||
NOT_IMPLEMENTED: notImplemented,
|
||||
OPERATION_NOT_SUPPORTED: operationNotSupported,
|
||||
ORGANIZER_ON_NON_TEAMDRIVE_NOT_SUPPORTED: organizerOnNonTeamDriveNotSupported,
|
||||
ORGANIZER_ON_NON_TEAMDRIVE_ITEM_NOT_SUPPORTED: organizerOnNonTeamDriveItemNotSupported,
|
||||
ORGUNIT_NOT_FOUND: orgunitNotFound,
|
||||
OUTSIDE_DOMAIN_MEMBER_CANNOT_CHANGE_TEAMDRIVE_RESTRICTIONS: outsideDomainMemberCannotChangeTeamDriveRestrictions,
|
||||
OWNER_ON_TEAMDRIVE_ITEM_NOT_SUPPORTED: ownerOnTeamDriveItemNotSupported,
|
||||
OWNERSHIP_CHANGE_ACROSS_DOMAIN_NOT_PERMITTED: ownershipChangeAcrossDomainNotPermitted,
|
||||
PARTICIPANT_IS_NEITHER_ORGANIZER_NOR_ATTENDEE: participantIsNeitherOrganizerNorAttendee,
|
||||
PERMISSION_DENIED: permissionDenied,
|
||||
PERMISSION_NOT_FOUND: permissionNotFound,
|
||||
PHOTO_NOT_FOUND: photoNotFound,
|
||||
PUBLISH_OUT_NOT_PERMITTED: publishOutNotPermitted,
|
||||
QUERY_REQUIRES_ADMIN_CREDENTIALS: queryRequiresAdminCredentials,
|
||||
QUOTA_EXCEEDED: quotaExceeded,
|
||||
RATE_LIMIT_EXCEEDED: rateLimitExceeded,
|
||||
REQUIRED: required,
|
||||
REQUIRED_ACCESS_LEVEL: requiredAccessLevel,
|
||||
RESOURCE_EXHAUSTED: resourceExhausted,
|
||||
RESOURCE_ID_NOT_FOUND: resourceIdNotFound,
|
||||
RESOURCE_NOT_FOUND: resourceNotFound,
|
||||
RESPONSE_PREPARATION_FAILURE: responsePreparationFailure,
|
||||
REVISION_DELETION_NOT_SUPPORTED: revisionDeletionNotSupported,
|
||||
REVISION_NOT_FOUND: revisionNotFound,
|
||||
REVISIONS_NOT_SUPPORTED: revisionsNotSupported,
|
||||
SERVICE_LIMIT: serviceLimit,
|
||||
SERVICE_NOT_AVAILABLE: serviceNotAvailable,
|
||||
SHARE_IN_NOT_PERMITTED: shareInNotPermitted,
|
||||
SHARE_OUT_NOT_PERMITTED: shareOutNotPermitted,
|
||||
SHARE_OUT_NOT_PERMITTED_TO_USER: shareOutNotPermittedToUser,
|
||||
SHARE_OUT_WARNING: shareOutWarning,
|
||||
SHARING_RATE_LIMIT_EXCEEDED: sharingRateLimitExceeded,
|
||||
SHORTCUT_TARGET_INVALID: shortcutTargetInvalid,
|
||||
STORAGE_QUOTA_EXCEEDED: storageQuotaExceeded,
|
||||
SYSTEM_ERROR: systemError,
|
||||
TARGET_USER_ROLE_LIMITED_BY_LICENSE_RESTRICTION: targetUserRoleLimitedByLicenseRestriction,
|
||||
TEAMDRIVE_ALREADY_EXISTS: teamDriveAlreadyExists,
|
||||
TEAMDRIVE_DOMAIN_USERS_ONLY_RESTRICTION: teamDriveDomainUsersOnlyRestriction,
|
||||
TEAMDRIVE_TEAM_MEMBERS_ONLY_RESTRICTION: teamDriveTeamMembersOnlyRestriction,
|
||||
TEAMDRIVE_FILE_LIMIT_EXCEEDED: teamDriveFileLimitExceeded,
|
||||
TEAMDRIVE_HIERARCHY_TOO_DEEP: teamDriveHierarchyTooDeep,
|
||||
TEAMDRIVE_MEMBERSHIP_REQUIRED: teamDriveMembershipRequired,
|
||||
TEAMDRIVES_FOLDER_MOVE_IN_NOT_SUPPORTED: teamDrivesFolderMoveInNotSupported,
|
||||
TEAMDRIVES_FOLDER_SHARING_NOT_SUPPORTED: teamDrivesFolderSharingNotSupported,
|
||||
TEAMDRIVES_PARENT_LIMIT: teamDrivesParentLimit,
|
||||
TEAMDRIVES_SHARING_RESTRICTION_NOT_ALLOWED: teamDrivesSharingRestrictionNotAllowed,
|
||||
TEAMDRIVES_SHORTCUT_FILE_NOT_SUPPORTED: teamDrivesShortcutFileNotSupported,
|
||||
TIME_RANGE_EMPTY: timeRangeEmpty,
|
||||
TRANSIENT_ERROR: transientError,
|
||||
UNIMPLEMENTED_ERROR: unimplementedError,
|
||||
UNKNOWN_ERROR: unknownError,
|
||||
UNSUPPORTED_LANGUAGE_CODE: unsupportedLanguageCode,
|
||||
UNSUPPORTED_SUPERVISED_ACCOUNT: unsupportedSupervisedAccount,
|
||||
UPLOAD_TOO_LARGE: uploadTooLarge,
|
||||
USER_CANNOT_CREATE_TEAMDRIVES: userCannotCreateTeamDrives,
|
||||
USER_ACCESS: userAccess,
|
||||
USER_NOT_FOUND: userNotFound,
|
||||
USER_RATE_LIMIT_EXCEEDED: userRateLimitExceeded,
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2023 Ross Scroggs All Rights Reserved.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""GAM GData resources
|
||||
|
||||
"""
|
||||
API_DEPRECATED_MSG = 'Contacts API is being deprecated.'
|
||||
|
||||
# callGData throw errors
|
||||
API_DEPRECATED = 612
|
||||
BAD_GATEWAY = 601
|
||||
BAD_REQUEST = 602
|
||||
DOES_NOT_EXIST = 1301
|
||||
ENTITY_EXISTS = 1300
|
||||
FORBIDDEN = 603
|
||||
GATEWAY_TIMEOUT = 612
|
||||
INSUFFICIENT_PERMISSIONS = 604
|
||||
INTERNAL_SERVER_ERROR = 1000
|
||||
INVALID_DOMAIN = 605
|
||||
INVALID_INPUT = 1317
|
||||
INVALID_VALUE = 1801
|
||||
NAME_NOT_VALID = 1303
|
||||
NOT_FOUND = 606
|
||||
NOT_IMPLEMENTED = 607
|
||||
PRECONDITION_FAILED = 608
|
||||
QUOTA_EXCEEDED = 609
|
||||
SERVICE_NOT_APPLICABLE = 1410
|
||||
SERVICE_UNAVAILABLE = 610
|
||||
TOKEN_EXPIRED = 611
|
||||
TOKEN_INVALID = 403
|
||||
UNKNOWN_ERROR = 600
|
||||
#
|
||||
NON_TERMINATING_ERRORS = [API_DEPRECATED, BAD_GATEWAY, GATEWAY_TIMEOUT, QUOTA_EXCEEDED, SERVICE_UNAVAILABLE, TOKEN_EXPIRED]
|
||||
EMAILSETTINGS_THROW_LIST = [INVALID_DOMAIN, DOES_NOT_EXIST, SERVICE_NOT_APPLICABLE, BAD_REQUEST, NAME_NOT_VALID, INTERNAL_SERVER_ERROR, INVALID_VALUE]
|
||||
#
|
||||
class apiDeprecated(Exception):
|
||||
pass
|
||||
class badRequest(Exception):
|
||||
pass
|
||||
class doesNotExist(Exception):
|
||||
pass
|
||||
class entityExists(Exception):
|
||||
pass
|
||||
class forbidden(Exception):
|
||||
pass
|
||||
class insufficientPermissions(Exception):
|
||||
pass
|
||||
class internalServerError(Exception):
|
||||
pass
|
||||
class invalidDomain(Exception):
|
||||
pass
|
||||
class invalidInput(Exception):
|
||||
pass
|
||||
class invalidValue(Exception):
|
||||
pass
|
||||
class nameNotValid(Exception):
|
||||
pass
|
||||
class notFound(Exception):
|
||||
pass
|
||||
class notImplemented(Exception):
|
||||
pass
|
||||
class preconditionFailed(Exception):
|
||||
pass
|
||||
class serviceNotApplicable(Exception):
|
||||
pass
|
||||
|
||||
ERROR_CODE_EXCEPTION_MAP = {
|
||||
API_DEPRECATED: apiDeprecated,
|
||||
BAD_REQUEST: badRequest,
|
||||
DOES_NOT_EXIST: doesNotExist,
|
||||
ENTITY_EXISTS: entityExists,
|
||||
FORBIDDEN: forbidden,
|
||||
INSUFFICIENT_PERMISSIONS: insufficientPermissions,
|
||||
INTERNAL_SERVER_ERROR: internalServerError,
|
||||
INVALID_DOMAIN: invalidDomain,
|
||||
INVALID_INPUT: invalidInput,
|
||||
INVALID_VALUE: invalidValue,
|
||||
NAME_NOT_VALID: nameNotValid,
|
||||
NOT_FOUND: notFound,
|
||||
NOT_IMPLEMENTED: notImplemented,
|
||||
PRECONDITION_FAILED: preconditionFailed,
|
||||
SERVICE_NOT_APPLICABLE: serviceNotApplicable,
|
||||
}
|
||||
@@ -1,329 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2026 Ross Scroggs All Rights Reserved.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""GAM global variables
|
||||
|
||||
"""
|
||||
|
||||
# The following GM_XXX constants are arbitrary but must be unique
|
||||
# Most errors print a message and bail out with a return code
|
||||
# Some commands want to set a non-zero return code but not bail
|
||||
# GAM admin user from oauth2.txt or oauth2service.json
|
||||
ADMIN = 'admn'
|
||||
# Number/length of API call retries
|
||||
API_CALLS_RETRY_DATA = 'rtry'
|
||||
# GAM cache directory. If no_cache is True, this variable will be set to None
|
||||
CACHE_DIR = 'gacd'
|
||||
# Reset GAM cache directory after discovery
|
||||
CACHE_DISCOVERY_ONLY = 'gcdo'
|
||||
# Classroom owner service object
|
||||
CLASSROOM_OWNER_SA = 'cosa'
|
||||
# Classroom service not available
|
||||
CLASSROOM_SERVICE_NOT_AVAILABLE = 'csna'
|
||||
# Command logging
|
||||
CMDLOG_HANDLER = 'clha'
|
||||
CMDLOG_LOGGER = 'cllo'
|
||||
# Convert to local time
|
||||
CONVERT_TO_LOCAL_TIME = 'ctlt'
|
||||
# Credentials scopes
|
||||
CREDENTIALS_SCOPES = 'crsc'
|
||||
# csvfile keyfield <FieldName> [delimiter <Character>] (matchfield <FieldName> <MatchPattern>)* [datafield <FieldName>(:<FieldName>*) [delimiter <String>]]
|
||||
CSVFILE = 'csvf'
|
||||
# { key: [datafieldvalues]}
|
||||
CSV_DATA_DICT = 'csdd'
|
||||
CSV_KEY_FIELD = 'cskf'
|
||||
CSV_SUBKEY_FIELD = 'cssk'
|
||||
CSV_DATA_FIELD = 'csdf'
|
||||
# Filter for input column drop values
|
||||
CSV_INPUT_ROW_DROP_FILTER = 'cird'
|
||||
# Mode (and|or) for input column drop values
|
||||
CSV_INPUT_ROW_DROP_FILTER_MODE = 'cidm'
|
||||
# Filter for input column values
|
||||
CSV_INPUT_ROW_FILTER = 'cirf'
|
||||
# Mode (and|or) for input column values
|
||||
CSV_INPUT_ROW_FILTER_MODE = 'cirm'
|
||||
# Limit number of input rows
|
||||
CSV_INPUT_ROW_LIMIT = 'cirl'
|
||||
# Column delimiter in CSV output file
|
||||
CSV_OUTPUT_COLUMN_DELIMITER = 'codl'
|
||||
# Filter for output column headers to drop
|
||||
CSV_OUTPUT_HEADER_DROP_FILTER = 'cohd'
|
||||
# Filter for output column headers
|
||||
CSV_OUTPUT_HEADER_FILTER = 'cohf'
|
||||
# Force output column headers
|
||||
CSV_OUTPUT_HEADER_FORCE = 'cofh'
|
||||
# Order output column headers
|
||||
CSV_OUTPUT_HEADER_ORDER = 'coho'
|
||||
# Required output column headers
|
||||
CSV_OUTPUT_HEADER_REQUIRED = 'corh'
|
||||
# No escape character in CSV output file
|
||||
CSV_OUTPUT_NO_ESCAPE_CHAR = 'cone'
|
||||
# Quote character in CSV output file
|
||||
CSV_OUTPUT_QUOTE_CHAR = 'coqc'
|
||||
# Filter for output column drop values
|
||||
CSV_OUTPUT_ROW_DROP_FILTER = 'cord'
|
||||
# Mode (and|or) for output column drop values
|
||||
CSV_OUTPUT_ROW_DROP_FILTER_MODE = 'codm'
|
||||
# Filter for output column values
|
||||
CSV_OUTPUT_ROW_FILTER = 'corf'
|
||||
# Mode (and|or) for output column values
|
||||
CSV_OUTPUT_ROW_FILTER_MODE = 'corm'
|
||||
# Limit number of output rows
|
||||
CSV_OUTPUT_ROW_LIMIT = 'corl'
|
||||
# Add timestamp column to CSV output file
|
||||
CSV_OUTPUT_TIMESTAMP_COLUMN = 'cotc'
|
||||
# Transpose output rows/columns
|
||||
CSV_OUTPUT_TRANSPOSE = 'cotr'
|
||||
# Output sort headers
|
||||
CSV_OUTPUT_SORT_HEADERS = 'cosh'
|
||||
# CSV todrive options
|
||||
CSV_TODRIVE = 'todr'
|
||||
# Current API services
|
||||
CURRENT_API_SERVICES = 'caps'
|
||||
# Current Client API
|
||||
CURRENT_CLIENT_API = 'ccap'
|
||||
# Current Client API scopes
|
||||
CURRENT_CLIENT_API_SCOPES = 'ccas'
|
||||
# Current Service Account API
|
||||
CURRENT_SVCACCT_API = 'csap'
|
||||
# Current Service Account API scopes
|
||||
CURRENT_SVCACCT_API_SCOPES = 'csas'
|
||||
# Current Service Account user
|
||||
CURRENT_SVCACCT_USER = 'csa'
|
||||
# datetime.datetime.now
|
||||
DATETIME_NOW = 'dtno'
|
||||
# If debug_level > 0: extra_args['prettyPrint'] = True, httplib2.debuglevel = gam_debug_level, appsObj.debug = True
|
||||
DEBUG_LEVEL = 'dbgl'
|
||||
# Whether debug output should redact sensitive credentials
|
||||
DEBUG_REDACTION = 'dbrd'
|
||||
# Decoded ID token
|
||||
DECODED_ID_TOKEN = 'didt'
|
||||
# Developer Preview APIs
|
||||
DEVELOPER_PREVIEW_APIS = 'dapi'
|
||||
# Index of start of <UserTypeEntity> in command line
|
||||
ENTITY_CL_DELAY_START = 'ecld'
|
||||
ENTITY_CL_START = 'ecls'
|
||||
# Extra arguments to pass to GAPI functions
|
||||
EXTRA_ARGS_LIST = 'exad'
|
||||
# gam.cfg file
|
||||
GAM_CFG_FILE = 'gcfi'
|
||||
GAM_CFG_PATH = 'gcpa'
|
||||
GAM_CFG_SECTION = 'gcse'
|
||||
GAM_CFG_SECTION_NAME = 'gcsn'
|
||||
# Path to gam
|
||||
GAM_PATH = 'gpth'
|
||||
# Python source, PyInstaller or StaticX?
|
||||
GAM_TYPE = 'gtyp'
|
||||
# Shared Service Account HTTP Object
|
||||
HTTP_OBJECT = 'http'
|
||||
# Are we on Global Compute Engine
|
||||
IS_ON_GCE = 'ogce'
|
||||
# Length of last Got message
|
||||
LAST_GOT_MSG_LEN = 'lgml'
|
||||
# License SKUs
|
||||
LICENSE_SKUS = 'lsku'
|
||||
# Make Building ID/Name map
|
||||
MAKE_BUILDING_ID_NAME_MAP = 'mkbm'
|
||||
# Dictionary mapping Building ID to Name
|
||||
MAP_BUILDING_ID_TO_NAME = 'bi2n'
|
||||
# Dictionary mapping Building Name to ID
|
||||
MAP_BUILDING_NAME_TO_ID = 'bn2i'
|
||||
# Dictionary mapping OrgUnit ID to Name
|
||||
MAP_ORGUNIT_ID_TO_NAME = 'oi2n'
|
||||
# Dictionary mapping Shared Drive ID to Name
|
||||
MAP_SHAREDDRIVE_ID_TO_NAME = 'si2n'
|
||||
# Make Role ID/Name map
|
||||
MAKE_ROLE_ID_NAME_MAP = 'mkrm'
|
||||
# Dictionary mapping Role ID to Name
|
||||
MAP_ROLE_ID_TO_NAME = 'ri2n'
|
||||
# Dictionary mapping Role Name to ID
|
||||
MAP_ROLE_NAME_TO_ID = 'rn2i'
|
||||
# Dictionary mapping User ID to Name
|
||||
MAP_USER_ID_TO_NAME = 'ui2n'
|
||||
# Multiprocess exit condition
|
||||
MULTIPROCESS_EXIT_CONDITION = 'mpec'
|
||||
# Multiprocess exit processing
|
||||
MULTIPROCESS_EXIT_PROCESSING = 'mpep'
|
||||
# Number of batch items
|
||||
NUM_BATCH_ITEMS = 'nbat'
|
||||
# Values retrieved from oauth2service.json
|
||||
OAUTH2SERVICE_CLIENT_ID = 'osci'
|
||||
OAUTH2SERVICE_JSON_DATA = 'osjd'
|
||||
# Values retrieved from oauth2.txt
|
||||
OAUTH2_CLIENT_ID = 'oaci'
|
||||
# oauth2.txt lock file
|
||||
OAUTH2_TXT_LOCK = 'oatl'
|
||||
# Output date format, empty defalts to ISOFormat
|
||||
OUTPUT_DATEFORMAT = 'oudf'
|
||||
# Output time format, empty defalts to ISOFormat
|
||||
OUTPUT_TIMEFORMAT = 'outf'
|
||||
# gam.cfg parser
|
||||
PARSER = 'pars'
|
||||
# Process ID
|
||||
PID = 'pid '
|
||||
# Domains for print alises|groups|users
|
||||
PRINT_AGU_DOMAINS = 'pagu'
|
||||
# OrgUnits for print cros
|
||||
PRINT_CROS_OUS = 'pcou'
|
||||
# OrgUnits and children for print cros
|
||||
PRINT_CROS_OUS_AND_CHILDREN = 'pcoc'
|
||||
# Check API calls rate
|
||||
RATE_CHECK_COUNT = 'rccn'
|
||||
RATE_CHECK_START = 'rcst'
|
||||
# Section name from outer gam, passed to inner gams
|
||||
SECTION = 'sect'
|
||||
# Enable/disable "Getting ... " messages
|
||||
SHOW_GETTINGS = 'shog'
|
||||
# Enable/disable NL at end of "Got ..." messages
|
||||
SHOW_GETTINGS_GOT_NL = 'shgn'
|
||||
# redirected files
|
||||
SAVED_STDOUT = 'svso'
|
||||
STDERR = 'stde'
|
||||
STDOUT = 'stdo'
|
||||
# Scopes values retrieved from oauth2service.json
|
||||
SVCACCT_SCOPES = 'sasc'
|
||||
# Were scopes values retrieved from oauth2service.json
|
||||
SVCACCT_SCOPES_DEFINED = 'sasd'
|
||||
# Most errors print a message and bail out with a return code
|
||||
# Some commands want to set a non-zero return code but not bail
|
||||
SYSEXITRC = 'sxrc'
|
||||
# Encodings
|
||||
SYS_ENCODING = 'syen'
|
||||
# Shared by threadBatchWorker and threadBatchGAMCommands
|
||||
TBATCH_QUEUE = 'batq'
|
||||
# redirected file fields: name, mode, encoding, write header, multiproces, queue
|
||||
REDIRECT_NAME = 'rdfn'
|
||||
REDIRECT_MODE = 'rdmo'
|
||||
REDIRECT_FD = 'rdfd'
|
||||
REDIRECT_MULTI_FD = 'rdmf'
|
||||
REDIRECT_STD = 'rdst'
|
||||
REDIRECT_ENCODING = 'rden'
|
||||
REDIRECT_WRITE_HEADER = 'rdwh'
|
||||
REDIRECT_MULTIPROCESS = 'rdmp'
|
||||
REDIRECT_QUEUE = 'rdq'
|
||||
REDIRECT_QUEUE_NAME = 'name'
|
||||
REDIRECT_QUEUE_CLEAR_ROW_FILTERS = 'clearRowFilters'
|
||||
REDIRECT_QUEUE_TODRIVE = 'todrive'
|
||||
REDIRECT_QUEUE_CSVPF = 'csvpf'
|
||||
REDIRECT_QUEUE_DATA = 'rows'
|
||||
REDIRECT_QUEUE_ARGS = 'args'
|
||||
REDIRECT_QUEUE_GLOBALS = 'globals'
|
||||
REDIRECT_QUEUE_VALUES = 'values'
|
||||
REDIRECT_QUEUE_START = 'start'
|
||||
REDIRECT_QUEUE_END = 'end'
|
||||
REDIRECT_QUEUE_EOF = 'eof'
|
||||
#
|
||||
Globals = {
|
||||
ADMIN: None,
|
||||
API_CALLS_RETRY_DATA: {},
|
||||
CACHE_DIR: None,
|
||||
CACHE_DISCOVERY_ONLY: True,
|
||||
CLASSROOM_OWNER_SA: {},
|
||||
CLASSROOM_SERVICE_NOT_AVAILABLE: False,
|
||||
CMDLOG_HANDLER: None,
|
||||
CMDLOG_LOGGER: None,
|
||||
CONVERT_TO_LOCAL_TIME: False,
|
||||
CREDENTIALS_SCOPES: set(),
|
||||
CSVFILE: {},
|
||||
CSV_DATA_DICT: {},
|
||||
CSV_KEY_FIELD: None,
|
||||
CSV_SUBKEY_FIELD: None,
|
||||
CSV_DATA_FIELD: None,
|
||||
CSV_INPUT_ROW_DROP_FILTER: [],
|
||||
CSV_INPUT_ROW_DROP_FILTER_MODE: False,
|
||||
CSV_INPUT_ROW_FILTER: [],
|
||||
CSV_INPUT_ROW_FILTER_MODE: True,
|
||||
CSV_INPUT_ROW_LIMIT: 0,
|
||||
CSV_OUTPUT_COLUMN_DELIMITER: None,
|
||||
CSV_OUTPUT_HEADER_DROP_FILTER: [],
|
||||
CSV_OUTPUT_HEADER_FILTER: [],
|
||||
CSV_OUTPUT_HEADER_FORCE: [],
|
||||
CSV_OUTPUT_HEADER_ORDER: [],
|
||||
CSV_OUTPUT_HEADER_REQUIRED: [],
|
||||
CSV_OUTPUT_NO_ESCAPE_CHAR: None,
|
||||
CSV_OUTPUT_QUOTE_CHAR: None,
|
||||
CSV_OUTPUT_ROW_DROP_FILTER: [],
|
||||
CSV_OUTPUT_ROW_DROP_FILTER_MODE: False,
|
||||
CSV_OUTPUT_ROW_FILTER: [],
|
||||
CSV_OUTPUT_ROW_FILTER_MODE: True,
|
||||
CSV_OUTPUT_ROW_LIMIT: 0,
|
||||
CSV_OUTPUT_SORT_HEADERS: [],
|
||||
CSV_OUTPUT_TIMESTAMP_COLUMN: None,
|
||||
CSV_OUTPUT_TRANSPOSE: False,
|
||||
CSV_TODRIVE: {},
|
||||
CURRENT_API_SERVICES: {},
|
||||
CURRENT_CLIENT_API: None,
|
||||
CURRENT_CLIENT_API_SCOPES: set(),
|
||||
CURRENT_SVCACCT_API: None,
|
||||
CURRENT_SVCACCT_API_SCOPES: set(),
|
||||
CURRENT_SVCACCT_USER: None,
|
||||
DATETIME_NOW: None,
|
||||
DEBUG_LEVEL: 0,
|
||||
DEBUG_REDACTION: True,
|
||||
DECODED_ID_TOKEN: None,
|
||||
DEVELOPER_PREVIEW_APIS: set(),
|
||||
ENTITY_CL_DELAY_START: 1,
|
||||
ENTITY_CL_START: 1,
|
||||
EXTRA_ARGS_LIST: [],
|
||||
GAM_CFG_FILE: '',
|
||||
GAM_CFG_PATH: '',
|
||||
GAM_CFG_SECTION: '',
|
||||
GAM_CFG_SECTION_NAME: '',
|
||||
GAM_PATH: '.',
|
||||
GAM_TYPE: '',
|
||||
HTTP_OBJECT: None,
|
||||
IS_ON_GCE: False,
|
||||
LAST_GOT_MSG_LEN: 0,
|
||||
LICENSE_SKUS: [],
|
||||
MAKE_BUILDING_ID_NAME_MAP: True,
|
||||
MAKE_ROLE_ID_NAME_MAP: True,
|
||||
MAP_BUILDING_ID_TO_NAME: {},
|
||||
MAP_BUILDING_NAME_TO_ID: {},
|
||||
MAP_ORGUNIT_ID_TO_NAME: {},
|
||||
MAP_SHAREDDRIVE_ID_TO_NAME: {},
|
||||
MAP_ROLE_ID_TO_NAME: {},
|
||||
MAP_ROLE_NAME_TO_ID: {},
|
||||
MAP_USER_ID_TO_NAME: {},
|
||||
MULTIPROCESS_EXIT_CONDITION: None,
|
||||
MULTIPROCESS_EXIT_PROCESSING: False,
|
||||
NUM_BATCH_ITEMS: 0,
|
||||
OAUTH2SERVICE_CLIENT_ID: None,
|
||||
OAUTH2SERVICE_JSON_DATA: {},
|
||||
OAUTH2_CLIENT_ID: None,
|
||||
OAUTH2_TXT_LOCK: None,
|
||||
OUTPUT_DATEFORMAT: '',
|
||||
OUTPUT_TIMEFORMAT: '',
|
||||
PARSER: None,
|
||||
PID: 0,
|
||||
PRINT_AGU_DOMAINS: '',
|
||||
PRINT_CROS_OUS: '',
|
||||
PRINT_CROS_OUS_AND_CHILDREN: '',
|
||||
RATE_CHECK_COUNT: 0,
|
||||
RATE_CHECK_START: 0,
|
||||
SECTION: None,
|
||||
SHOW_GETTINGS: True,
|
||||
SHOW_GETTINGS_GOT_NL: False,
|
||||
SAVED_STDOUT: None,
|
||||
STDERR: {},
|
||||
STDOUT: {},
|
||||
SVCACCT_SCOPES: {},
|
||||
SVCACCT_SCOPES_DEFINED: False,
|
||||
SYSEXITRC: 0,
|
||||
SYS_ENCODING: 'utf-8',
|
||||
TBATCH_QUEUE: None
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2023 Ross Scroggs All Rights Reserved.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""GAM indent processing
|
||||
|
||||
"""
|
||||
|
||||
class GamIndent():
|
||||
|
||||
INDENT_SPACES_PER_LEVEL = ' '
|
||||
|
||||
def __init__(self):
|
||||
self.indent = 0
|
||||
|
||||
def Reset(self):
|
||||
self.indent = 0
|
||||
|
||||
def Increment(self):
|
||||
self.indent += 1
|
||||
|
||||
def Decrement(self):
|
||||
self.indent -= 1
|
||||
|
||||
def Spaces(self):
|
||||
return self.INDENT_SPACES_PER_LEVEL*self.indent
|
||||
|
||||
def SpacesSub1(self):
|
||||
return self.INDENT_SPACES_PER_LEVEL*(self.indent-1)
|
||||
|
||||
def MultiLineText(self, message, n=0):
|
||||
return message.replace('\n', f'\n{self.INDENT_SPACES_PER_LEVEL*(self.indent+n)}').rstrip()
|
||||
@@ -1,569 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2026 Ross Scroggs All Rights Reserved.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""GAM messages
|
||||
|
||||
"""
|
||||
|
||||
# These values can be translated into other languages
|
||||
# Project creation messages in order of appearance
|
||||
CREATING_PROJECT = 'Creating project "{0}"...\n'
|
||||
CHECK_INTERRUPTED = 'Check interrupted'
|
||||
CHECKING_PROJECT_CREATION_STATUS = 'Checking project creation status...\n'
|
||||
NO_RIGHTS_GOOGLE_CLOUD_ORGANIZATION = 'Looks like you have no rights to your Google Cloud Organization.\nAttempting to fix that...\n'
|
||||
YOUR_ORGANIZATION_NAME_IS = 'Your organization name is {0}\n'
|
||||
YOU_HAVE_NO_RIGHTS_TO_CREATE_PROJECTS_AND_YOU_ARE_NOT_A_SUPER_ADMIN = 'You have no rights to create projects for your organization and you don\'t seem to be a super admin! Sorry, there\'s nothing more I can do.'
|
||||
LOOKS_LIKE_NO_ONE_HAS_RIGHTS_TO_YOUR_GOOGLE_CLOUD_ORGANIZATION_ATTEMPTING_TO_GIVE_YOU_CREATE_RIGHTS = 'Looks like no one has rights to your Google Cloud Organization. Attempting to give you create rights...\n'
|
||||
THE_FOLLOWING_RIGHTS_SEEM_TO_EXIST = 'The following rights seem to exist:\n'
|
||||
GIVING_LOGIN_HINT_THE_CREATOR_ROLE = 'Giving {0} the role of {1}...\n'
|
||||
ACCEPT_CLOUD_TOS = '''
|
||||
Please go to:
|
||||
|
||||
https://console.cloud.google.com/projectselector2/home/dashboard?supportedpurview=project
|
||||
|
||||
sign in as {0} and accept the Terms of Service (ToS). As soon as you've accepted the ToS popup, you can return here and press enter.\n'''
|
||||
|
||||
PROJECT_STILL_BEING_CREATED_SLEEPING = 'Project still being created. Sleeping {0} seconds\n'
|
||||
FAILED_TO_CREATE_PROJECT = 'Failed to create project: {0}\n'
|
||||
SETTING_GAM_PROJECT_CONSENT_SCREEN_CREATING_CLIENT = 'Setting GAM project consent screen, creating client...\n'
|
||||
CREATE_CLIENT_INSTRUCTIONS = '''
|
||||
Please go to:
|
||||
|
||||
{0}
|
||||
|
||||
1. If "+ CREATE CLIENT" is on the screen, skip to step 14
|
||||
2. Click "GET STARTED"
|
||||
3. Under "App Information", enter {1} or another value in "App name *"
|
||||
4. Under "App Information", enter {2} in "User support email *"
|
||||
5. Click "NEXT"
|
||||
6. Under "Audience", choose INTERNAL
|
||||
7. Click "NEXT"
|
||||
8. Under, "Contact Information", enter {2} or another value in "Email addresses *"
|
||||
9. Click "NEXT"
|
||||
10. Under "Finish", click "I agree to the Google API Services: User Data Policy."
|
||||
11. Click "CONTINUE"
|
||||
12. Click "CREATE"
|
||||
13. Click "Clients" in the left-hand column
|
||||
14. Click "+ CREATE CLIENT"
|
||||
15. Choose "Desktop App" for "Application type"
|
||||
16. Enter {1} or another value in "Name *"
|
||||
17. Click "Create"
|
||||
18. Under "Name", click your client name
|
||||
19. Copy the "Client ID" value under "Additional information"
|
||||
20. Paste it at the "Enter your Client ID: " prompt in your terminal
|
||||
21. Press return/enter in your terminal
|
||||
22. Switch back to the browser
|
||||
23. Copy the "Client secret" value under "Client Secrets"
|
||||
24. Paste it at the "Enter your Client Secret: " prompt in your terminal
|
||||
25. Press return/enter in your terminal
|
||||
26. Switch back to the browser
|
||||
27. Click "OK"
|
||||
28. These steps are complete
|
||||
'''
|
||||
ENTER_YOUR_CLIENT_ID = '\nEnter your Client ID: '
|
||||
ENTER_YOUR_CLIENT_SECRET = '\nEnter your Client Secret: '
|
||||
IS_NOT_A_VALID_CLIENT_ID = '''
|
||||
|
||||
{0}
|
||||
|
||||
Is not a valid Client ID.
|
||||
|
||||
Please make sure you are following the directions exactly and that there are no extra spaces in your Client ID.
|
||||
'''
|
||||
IS_NOT_A_VALID_CLIENT_SECRET = '''
|
||||
|
||||
{0}
|
||||
|
||||
Is not a valid Client Secret.
|
||||
|
||||
Please make sure you are following the directions exactly and that there are no extra spaces in your Client Secret.
|
||||
'''
|
||||
TRUST_GAM_CLIENT_ID = '''
|
||||
It's important to mark the {0} Client ID as trusted by your Workspace instance.
|
||||
|
||||
Please go to:
|
||||
|
||||
https://admin.google.com/ac/owl/list?tab=configuredApps
|
||||
|
||||
1. Click on: Configure new app
|
||||
2. Enter the following Client ID value in Search for app:
|
||||
|
||||
{1}
|
||||
|
||||
3. Press Search, select the {0} app, click
|
||||
4. Keep the default scope or select a preferred scope that includes your GAM admin.
|
||||
5. Press Continue
|
||||
6. Select Trusted radio button, press Continue and Finish.
|
||||
7. Press Confirm if Confirm parental consent pops up
|
||||
8. Press enter here on the terminal once trust is complete.
|
||||
'''
|
||||
|
||||
ENABLE_SERVICE_ACCOUNT_PRIVATE_KEY_UPLOAD = '''
|
||||
Your workspace is configured to disable service account private key uploads.
|
||||
|
||||
Please go to:
|
||||
|
||||
https://github.com/GAM-team/GAM/wiki/Authorization#authorize-service-account-key-uploads
|
||||
|
||||
Follow the steps to allow a service account private key upload for the project ({0}) just created.
|
||||
Once those steps are completed, you can continue with your project authentication.
|
||||
'''
|
||||
|
||||
YOUR_GAM_PROJECT_IS_CREATED_AND_READY_TO_USE = '''
|
||||
That\'s it! Your GAM Project is created and ready to use.
|
||||
Proceed to the authentication steps.
|
||||
'''
|
||||
|
||||
# check|update service messages in order of appearance
|
||||
SYSTEM_TIME_STATUS = 'System time status'
|
||||
YOUR_SYSTEM_TIME_DIFFERS_FROM_GOOGLE = 'Your system time differs from {0} by {1}'
|
||||
PRESS_ENTER_ONCE_AUTHORIZATION_IS_COMPLETE = 'Press enter once authorization is complete.'
|
||||
SERVICE_ACCOUNT_API_DISABLED = '{0} not enabled. Please run "gam update project" and "gam user user@domain.com update serviceaccount"'
|
||||
SERVICE_ACCOUNT_PRIVATE_KEY_AUTHENTICATION = 'Service Account Private Key Authentication'
|
||||
SERVICE_ACCOUNT_CHECK_PRIVATE_KEY_AGE = 'Service Account Private Key age; Google recommends rotating keys on a routine basis'
|
||||
SERVICE_ACCOUNT_PRIVATE_KEY_AGE = 'Service Account Private Key age: {0} days'
|
||||
SERVICE_ACCOUNT_SKIPPING_KEY_AGE_CHECK = 'Skipping Private Key age check: {0} rotation not necessary'
|
||||
UPDATE_PROJECT_TO_VIEW_MANAGE_SAKEYS = 'Please run "gam update project" to view/manage service account keys'
|
||||
DOMAIN_WIDE_DELEGATION_AUTHENTICATION = 'Domain-wide Delegation authentication'
|
||||
DEPRECATED_SCOPES = 'Deprecated scopes that GAM should NEVER have DwD access to'
|
||||
SCOPE_AUTHORIZATION_PASSED = '''All scopes PASSED!
|
||||
|
||||
Service Account Client name: {0} is fully authorized.
|
||||
'''
|
||||
SCOPE_AUTHORIZATION_UPDATE_PASSED = '''All scopes PASSED!
|
||||
To update authorization (in case some scopes were unselected), please go to the following link in your browser:
|
||||
{0}
|
||||
{1}
|
||||
|
||||
You will be directed to the Google Workspace admin console Security > API Controls > Domain-wide Delegation page
|
||||
The "Add a new Client ID" box will open
|
||||
Make sure that "Overwrite existing client ID" is checked
|
||||
Click AUTHORIZE
|
||||
When the box closes you're done
|
||||
After authorizing it may take some time for this test to pass so wait a few moments and then try this command again.
|
||||
'''
|
||||
SCOPE_AUTHORIZATION_FAILED = '''Some scopes FAILED or should be DISABLED!
|
||||
To update authorization, please go to the following link in your browser:
|
||||
{0}
|
||||
{1}
|
||||
|
||||
You will be directed to the Google Workspace admin console Security > API Controls > Domain-wide Delegation page
|
||||
The "Add a new Client ID" box will open
|
||||
Make sure that "Overwrite existing client ID" is checked
|
||||
Click AUTHORIZE
|
||||
When the box closes you're done
|
||||
After authorizing it may take some time for this test to pass so wait a few moments and then try this command again.
|
||||
'''
|
||||
# General messages
|
||||
ACCESS_FORBIDDEN = 'Access Forbidden'
|
||||
ACTION_APPLIED = 'Action Applied'
|
||||
ACTION_IN_PROGRESS = 'Action {0} in progress'
|
||||
ACTION_MAY_BE_DELAYED = 'Action may be delayed'
|
||||
ADMIN_STATUS_CHANGED_TO = 'Admin Status Changed to'
|
||||
ALL = 'All'
|
||||
ALL_FOLDER_NAMES_MUST_BE_NON_BLANK = 'All folder names must be non-blank: {0}'
|
||||
ALL_SKU_PRODUCTIDS_MUST_MATCH = 'All SKU productIds must match, {0} != {1}'
|
||||
ALREADY_WAS_OWNER = 'Already was owner'
|
||||
ALREADY_EXISTS = 'Already exists'
|
||||
ALREADY_EXISTS_IN_TARGET_FOLDER = 'Already exists in {0}: {1}'
|
||||
ALREADY_EXISTS_USE_MERGE_ARGUMENT = 'Already exists; use the "merge" argument to merge the labels'
|
||||
API_ACCESS_DENIED = 'API access Denied'
|
||||
API_CALLS_RETRY_DATA = 'API calls retry data\n'
|
||||
API_CHECK_CLIENT_AUTHORIZATION = 'Please make sure the Client ID: {0} is authorized for the appropriate API or scopes: {1}\n\nRun: gam oauth create\n'
|
||||
API_CHECK_SVCACCT_AUTHORIZATION = 'Please make sure the Service Account Client ID: {0} is authorized for the appropriate API or scopes: {1}\n\nRun: gam user {2} update serviceaccount\n'
|
||||
API_ERROR_SETTINGS = 'API error, some settings not set'
|
||||
ARE_BOTH_REQUIRED = 'Arguments {0} and {1} are both required'
|
||||
ARE_MUTUALLY_EXCLUSIVE = 'Arguments {0} and {1} are mutually exclusive'
|
||||
AS = 'as'
|
||||
ATTENDEES_ADD = 'Add Attendees'
|
||||
ATTENDEES_ADD_REMOVE = 'Add/Remove Attendees'
|
||||
ATTENDEES_REMOVE = 'Remove Attendees'
|
||||
AUTHORIZATION_FILE_ALREADY_EXISTS = '{0} already exists. Please delete or rename it before attempting to {1} project.'
|
||||
AUTHENTICATION_FLOW_COMPLETE = '\nThe authentication flow has completed.'
|
||||
AUTHENTICATION_FLOW_COMPLETE_CLOSE_BROWSER = 'The authentication flow has completed. You may close this browser window and return to {0}.'
|
||||
AUTHENTICATION_FLOW_FAILED = 'The authentication flow failed, reissue command'
|
||||
BAD_ENTITIES_IN_SOURCE = '{0} {1} {2} in source marked >>> <<< above'
|
||||
BAD_REQUEST = 'Bad Request'
|
||||
BATCH = 'Batch'
|
||||
BATCH_CSV_LOOP_DASH_DEBUG_INCOMPATIBLE = '"gam {0} - ..." is not compatible with debugging. Disable debugging by setting debug_level = 0 in gam.cfg'
|
||||
BATCH_CSV_PROCESSING_COMPLETE = '{0},0/{1},Processing complete\n'
|
||||
BATCH_CSV_TERMINATE_N_PROCESSES = '{0},0/{1},Terminating {2} running {3}\n'
|
||||
BATCH_CSV_WAIT_LIMIT = ', wait limit {0} seconds'
|
||||
BATCH_CSV_WAIT_N_PROCESSES = '{0},0/{1},Waiting for {2} running {3} to finish before terminating{4}\n'
|
||||
BATCH_NOT_PROCESSED_ERRORS = '{0}batch file: {1}, not processed, {2} {3}\n'
|
||||
CALLING_GCLOUD_FOR_REAUTH = 'Calling gcloud for reauth credentials..."\n'
|
||||
CAN_NOT_DELETE_USER_WITH_VAULT_HOLD = '{0}: The user may be (or have recently been) on Google Vault Hold and thus not eligible for deletion. You can check holds with "gam user {1} show vaultholds".'
|
||||
CAN_NOT_BE_SPECIFIED_MORE_THAN_ONCE = 'Argument {0} can not be specified more than once'
|
||||
CHAT_ADMIN_ACCESS_LIMITED_TO_ONE_USER = 'Chat adminaccess|asadmin limited to one user, {0} specified'
|
||||
CHROME_TARGET_VERSION_FORMAT = r'^([a-z]+)-(\d+)$ or ^(\d{1,4}\.){1,4}$'
|
||||
COLUMN_DOES_NOT_MATCH_ANY_INPUT_COLUMNS = '{0} column "{1}" does not match any input columns'
|
||||
COLUMN_DOES_NOT_MATCH_ANY_OUTPUT_COLUMNS = '{0} column "{1}" does not match any output columns'
|
||||
COMMAND_NOT_COMPATIBLE_WITH_ENABLE_DASA = 'gam {0} {1} is not compatible with enable_dasa = true in gam.cfg'
|
||||
COMMIT_BATCH_COMPLETE = '{0},0/{1},commit-batch - running {2} finished, proceeding\n'
|
||||
COMMIT_BATCH_WAIT_N_PROCESSES = '{0},0/{1},commit-batch - waiting for {2} running {3} to finish before proceeding\n'
|
||||
CONFIRM_WIPE_YUBIKEY_PIV = 'This will wipe all YubiKey PIV keys and configuration from your YubiKey. Are you sure? (y/N) '
|
||||
CONTACT_ADMINISTRATOR_FOR_PASSWORD = 'Contact administrator for password'
|
||||
CONTACT_PHOTO_NOT_FOUND = 'Contact photo not found'
|
||||
CONTAINS_AT_LEAST_1_ITEM = 'Contains at least 1 item'
|
||||
COUNT_N_EXCEEDS_MAX_TO_PROCESS_M = 'Count {0} exceeds maximum to {1} {2}'
|
||||
CORRUPT_FILE = 'Corrupt file'
|
||||
COULD_NOT_FIND_ANY_YUBIKEY = 'Could not find any YubiKey\n'
|
||||
COULD_NOT_FIND_YUBIKEY_WITH_SERIAL = 'Could not find YubiKey with serial number {0}\n'
|
||||
CREATE_DELEGATE_NOTIFY_MESSAGE = '#user# has granted you #delegate# access to read, delete and send mail on their behalf.'
|
||||
CREATE_DELEGATE_NOTIFY_SUBJECT = '#user# mail delegation to #delegate#'
|
||||
CREATE_USER_NOTIFY_MESSAGE = 'Hello #givenname# #familyname#,\n\nYou have a new account at #domain#\nAccount details:\nUsername: #user#\nPassword: #password#\nStart using your new account by signing in at\nhttps://www.google.com/accounts/AccountChooser?Email=#user#&continue=https://workspace.google.com/dashboard\n'
|
||||
CREATE_USER_NOTIFY_SUBJECT = 'Welcome to #domain#'
|
||||
CSV_DATA_ALREADY_SAVED = 'CSV data already saved'
|
||||
CSV_FILE_HEADERS = 'The CSV file ({0}) has the following headers:\n'
|
||||
CSV_SAMPLE_COMMANDS = 'Here are the first {0} commands {1} will run\n'
|
||||
DATA_FIELD_MISMATCH = 'datafield {0} does not match saved datafield {1}'
|
||||
DATA_TRANSFER_COMPLETED = 'Data Transfer completed: {0}\n'
|
||||
DATA_UPLOADED_TO_DRIVE_FILE = 'Data uploaded to Drive File'
|
||||
DEFAULT_SMIME = 'Default S/MIME'
|
||||
DELETED = 'Deleted'
|
||||
DEVELOPER_PREVIEW_REQUIRED = 'Developer Preview is required for this command\n'
|
||||
DEVICE_LIST_BUG = 'GAM hit Google internal bug 237397223. Please file a Google Support ticket stating that you are encountering this bug.'
|
||||
DEVICE_LIST_BUG_WORKAROUND_NOT_POSSIBLE = 'GAM workaround for this issue only works if orderby argument is not used and query does not contain \'register\'.'
|
||||
DEVICE_LIST_BUG_ATTEMPTING_WORKAROUND = 'GAM is attempting to work around the bug by filtering for devices created on or after the newest we\'ve seen ({0})...\n'
|
||||
DIRECTLY_IN_THE = ' directly in the {0}'
|
||||
DISABLE_TLS_MIN_MAX = 'Execute: gam select default config tls_max_version "" tls_min_version "" save\n'
|
||||
DISPLAYNAME_NOT_ALLOWED_WHEN_UPDATING_MULTIPLE_SCHEMAS = 'displayname not allowed when updating multiple schemas'
|
||||
DOES_NOT_EXIST = 'Does not exist'
|
||||
DOES_NOT_EXIST_OR_HAS_INVALID_FORMAT = '{0}: {1}, Does not exist or has invalid format, {2}'
|
||||
DOES_NOT_MATCH = 'Does not match {0}'
|
||||
DOMAIN_NOT_FOUND_IN_DNS = 'Domain not found in DNS!'
|
||||
DOMAIN_NOT_VERIFIED_SECONDARY = 'Domain is not a verified secondary domain'
|
||||
DONE_GENERATING_PRIVATE_KEY_AND_PUBLIC_CERTIFICATE = 'Done generating private key and public certificate'
|
||||
DO_NOT_EXIST = 'Do not exist'
|
||||
DOWNLOADING_AGAIN_AND_OVER_WRITING = 'Downloading again and over-writing...'
|
||||
DUPLICATE = 'Duplicate'
|
||||
DUPLICATE_ALREADY_A_ROLE = 'Duplicate, already a {0}'
|
||||
DYNAMIC_GROUP_MEMBERSHIP_CANNOT_BE_MODIFIED = 'Dynamic group membership cannot be modified'
|
||||
EITHER = 'Either'
|
||||
EMAIL_ADDRESS_IS_UNMANAGED_ACCOUNT = 'The email address is an unmanaged account'
|
||||
ENABLE_PROJECT_APIS_AUTOMATICALLY_OR_MANUALLY = 'Do you want to enable project APIs [a]utomatically or [m]anually? (a/m): '
|
||||
ENTER_GSUITE_ADMIN_EMAIL_ADDRESS = '\nEnter your Google Workspace admin email address? '
|
||||
ENTER_MANAGE_GCP_PROJECT_EMAIL_ADDRESS = '\nEnter your Google Workspace admin or GCP project manager email address authorized to manage project(s): {0}? '
|
||||
ENTER_VERIFICATION_CODE_OR_URL = 'Enter verification code or paste "Unable to connect" URL from other computer (only URL data up to &scope required): '
|
||||
ENTITY_DOES_NOT_EXIST = '{0} does not exist'
|
||||
ENTITY_NAME_NOT_VALID = 'Entity Name Not Valid'
|
||||
ERROR = 'error'
|
||||
ERRORS = 'errors'
|
||||
EVENT_IS_CANCELED = 'Event is canceled'
|
||||
EXECUTE_GAM_OAUTH_CREATE = '\nPlease run\n\ngam oauth delete\ngam oauth create\n\n'
|
||||
EXISTS = 'Exists'
|
||||
EXPECTED = 'Expected'
|
||||
EXPORT_NOT_COMPLETE = 'Export needs to be complete before downloading, current status is: {0}'
|
||||
EXTRACTING_PUBLIC_CERTIFICATE = 'Extracting public certificate'
|
||||
FAILED_PRECONDITION = 'Failed precondition'
|
||||
FAILED_TO_PARSE_AS_JSON = 'Failed to parse as JSON'
|
||||
FAILED_TO_PARSE_AS_LIST = 'Failed to parse as list'
|
||||
FIELD_NOT_FOUND_IN_SCHEMA = 'Field {0} not found in schema {1}'
|
||||
FILE_NOT_FOUND = 'File {0} not found'
|
||||
FINISHED = 'Finished'
|
||||
FILTER_CAN_ONLY_CONTAIN_ONE_CATEGORY_LABEL = 'Filter can only contain one CATEGORY label'
|
||||
FILTER_CAN_ONLY_CONTAIN_ONE_USER_LABEL = 'Filter can only contain one USER label'
|
||||
FOR = 'for'
|
||||
FORBIDDEN = 'Forbidden'
|
||||
FORMAT_NOT_AVAILABLE = 'Format ({0}) not available'
|
||||
FORMAT_NOT_DOWNLOADABLE = 'Format not downloadable'
|
||||
FROM = 'From'
|
||||
FROM_LC = 'from'
|
||||
FULL_PATH_MUST_START_WITH_DRIVE = 'fullpath must start with {0} or {1}'
|
||||
GAM_BATCH_FILE_WRITTEN = 'GAM batch file {0} written\n'
|
||||
GAM_LATEST_VERSION_NOT_AVAILABLE = 'GAM Latest Version information not available'
|
||||
GAM_OUT_OF_MEMORY = 'GAM has run out of memory. If this is a large Google Workspace instance, you should use a 64-bit version of GAM on Windows or a 64-bit version of Python on other systems.'
|
||||
GENERATING_NEW_PRIVATE_KEY = 'Generating new private key'
|
||||
GETTING = 'Getting'
|
||||
GETTING_ALL = 'Getting all'
|
||||
GRANTING_RIGHTS_TO_ROTATE_ITS_OWN_PRIVATE_KEY = '{0} rights to rotate its own private key'
|
||||
GOOGLE_DELEGATION_ERROR = 'Google delegation error, delegator and delegate both exist and are valid for delegation'
|
||||
GOT = 'Got'
|
||||
GROUP_MAPS_TO_MULTIPLE_OUS = 'File: {0}, Group: {1} references multiple OUs: {2}'
|
||||
GROUP_MAPS_TO_OU_INVALID_ROW = 'File: {0}, Invalid row, must contain non-blank <EmailAddress> and <OrgUnitPath>: <{1}> <{2}>'
|
||||
GUARDIAN_INVITATION_STATUS_NOT_PENDING = 'Guardian invitation status is not PENDING'
|
||||
HAS_CHILD_ORGS = 'Has child {0}'
|
||||
HAS_INVALID_FORMAT = '{0}: {1}, Has invalid format'
|
||||
HEADER_NOT_FOUND_IN_CSV_HEADERS = 'Header "{0}" not found in CSV headers of "{1}".'
|
||||
HELP_SYNTAX = 'Help: Syntax in file {0}\n'
|
||||
HELP_WIKI = 'Help: Documentation is at {0}\n'
|
||||
IGNORED = 'Ignored'
|
||||
INSTRUCTIONS_CLIENT_SECRETS_JSON = 'Please run\n\ngam create|use project\ngam oauth create\n\nto create and authorize a Client account.\n'
|
||||
INSTRUCTIONS_OAUTH2SERVICE_JSON = 'Please run\n\ngam create|use project\ngam user <user> update serviceaccount\n\nto create and authorize a Service account.\n'
|
||||
INSUFFICIENT_PERMISSIONS_TO_PERFORM_TASK = 'Insufficient permissions to perform this task'
|
||||
INTER_BATCH_WAIT_INCREASED = 'inter_batch_wait increased to {0:.2f}'
|
||||
INVALID = 'Invalid'
|
||||
INVALID_ALIAS = 'Invalid Alias'
|
||||
INVALID_ATTENDEE_CHANGE = 'Invalid attendee change "{0}"'
|
||||
INVALID_CHARSET = 'Invalid charset "{0}"'
|
||||
INVALID_DATE_TIME_RANGE = '{0} {1} must be greater than/equal to {2} {3}'
|
||||
INVALID_DEVICE_QUERY = 'Invalid {0} query "{1}"; it must be if the form "field:value" and must not contain a "?"'
|
||||
INVALID_EMOJI_NAME = '{0} does not match pattern :[0-9a-z_-]:'
|
||||
INVALID_ENTITY = 'Invalid {0}, {1}'
|
||||
INVALID_EVENT_TIMERANGE = '{0} {1} must be less than {2}'
|
||||
INVALID_FILE_SELECTION_WITH_ADMIN_ACCESS = 'Invalid file selection with adminaccess|asadmin'
|
||||
INVALID_GROUP = 'Invalid Group'
|
||||
INVALID_HTTP_HEADER = 'Invalid http header data: {0}'
|
||||
INVALID_JSON_INFORMATION = 'Google API reported Invalid JSON Information'
|
||||
INVALID_JSON_SETTING = 'Invalid JSON setting'
|
||||
INVALID_LIST = 'Invalid list'
|
||||
INVALID_MEMBER = 'Invalid Member address'
|
||||
INVALID_MESSAGE_ID = 'Invalid message id(s)'
|
||||
INVALID_MIMETYPE = 'Invalid mimeType {0}, must be {1}'
|
||||
INVALID_NUMBER_OF_CHAT_SPACE_MEMBERS = '{0} type {1} number of members, {2}, must be in range {3} to {4}'
|
||||
INVALID_ORGUNIT = 'Invalid Organizational Unit'
|
||||
INVALID_PATH = 'Invalid Path'
|
||||
INVALID_PERMISSION_ATTRIBUTE_TYPE = 'permission attribute {0} not allowed with type {1}'
|
||||
INVALID_REGION = 'See: https://github.com/GAM-team/GAM/wiki/Context-Aware-Access-Levels#caa-region-codes'
|
||||
INVALID_QUERY = 'Invalid Query'
|
||||
INVALID_RE = 'Invalid RE'
|
||||
INVALID_REQUEST = 'Invalid Request'
|
||||
INVALID_RESELLER_CUSTOMER_NAME = 'name must be: accounts/<ResellerID>/customers/<ChannelCustomerID>'
|
||||
INVALID_ROLE = 'Invalid subkeyfield Role, must be one of: {0}'
|
||||
INVALID_SCHEMA_VALUE = 'Invalid Schema Value'
|
||||
INVALID_SCOPE = 'Invalid Scope'
|
||||
INVALID_SITE = 'Invalid Site ({0}), must match pattern ({1})'
|
||||
INVALID_TAG_SPECIFICATION = 'Invalid tag, expected field.subfield or field.subfield.subfield.string'
|
||||
INVALID_TIMEOFDAY_RANGE = '{0} must be less than/equal to {1}'
|
||||
IN_SKIPIDS = 'In skipids'
|
||||
IN_THE = ' in the {0}'
|
||||
IN_TRASH_AND_EXCLUDE_TRASHED = 'In Trash and excludeTrashed'
|
||||
IS_EXPIRED_OR_REVOKED = '{0}: {1}, Is expired or has been revoked'
|
||||
IS_NOT_DONE_CHECKING_IN_SECONDS = 'Is not done, checking again in {0} seconds'
|
||||
IS_NOT_UNIQUE = 'Is not unique, {0}: {1}'
|
||||
IS_REQD_TO_CHG_PWD_NO_DELEGATION = 'Is required to change password at next login. You must change password or clear changepassword flag for delegation.'
|
||||
IS_SUSPENDED_NO_BACKUPCODES = 'User is suspended. You must unsuspend to process backupcodes'
|
||||
IS_SUSPENDED_NO_DELEGATION = 'Is suspended. You must unsuspend for delegation.'
|
||||
IS_YUBIKEY_INSERTED = 'Is YubiKey inserted?'
|
||||
JSON_ERROR = 'JSON error "{0}" in file {1}'
|
||||
JSON_KEY_NOT_FOUND = 'JSON key "{0}" not found in file {1}'
|
||||
KIOSK_MODE_REQUIRED = ' This command ({0}) requires that the ChromeOS device be in Kiosk mode.'
|
||||
LESS_THAN_1_SECOND = 'less than 1 second'
|
||||
LIST_CHROMEOS_INVALID_INPUT_PAGE_TOKEN_RETRY = 'List ChromeOSdevices Invalid Input: pageToken retry'
|
||||
LOGGING_INITIALIZATION_ERROR = 'Logging initialization error: {0}'
|
||||
LOOKING_UP_GOOGLE_UNIQUE_ID = 'Looking up Google Unique ID'
|
||||
MAP_PERMISSIONS_EMAIL_FILE_HEADERS_REQUIRED = '{0} <CSVFileInput> requires headers "sourceEmail" and "destinationEmail"'
|
||||
MARKED_AS = 'Marked as'
|
||||
MATCHED_THE_FOLLOWING = 'Matched the following'
|
||||
MATTER_NOT_OPEN = 'Matter needs to be open, current state is: {0}'
|
||||
MAXIMUM_OF = 'maximum of'
|
||||
MEMBERSHIP_IS_PENDING_WILL_DELETE_ADD_TO_ACCEPT = 'Membership is pending, will delete and add to accept'
|
||||
MIMETYPE_MISMATCH = 'Shortcut target mimeType {0} does not match actual target mimeType {1}'
|
||||
MIMETYPE_NOT_PRESENT_IN_ATTACHMENT = 'MIME type not present in attachment'
|
||||
MISMATCH_RE_SEARCH_REPLACE_SUBFIELDS = 'The subfield ({2}) in replace "{3}" exceeds the number of subfields ({0}) in search "{1}"'
|
||||
MISMATCH_SEARCH_REPLACE_SUBFIELDS = 'The number of subfields ({0}) in search "{1}" does not match the number of subfields ({2}) in replace "{3}"'
|
||||
MISSING_FIELDS = 'Missing fields: {0}\n'
|
||||
MULTIPLE_BUILDINGS_SAME_NAME = '{0} {1} with the same (case-insensitive) name exist'
|
||||
MULTIPLE_ENTITIES_FOUND = 'Multiple {0} ({1}) found, {2}'
|
||||
MULTIPLE_ITEMS_SPECIFIED = 'Multiple {0} are specfied, only one is allowed'
|
||||
MULTIPLE_ITEMS_MARKED_PRIMARY = 'Multiple {0} are marked primary, only one can be primary'
|
||||
MULTIPLE_PARENTS_SPECIFIED = 'Multiple parents ({0}) specified, only one is allowed'
|
||||
MULTIPLE_SEARCH_METHODS_SPECIFIED = 'Multiple search methods ({0}) specified, only one is allowed'
|
||||
MULTIPLE_SSO_PROFILES_MATCH = 'Multiple SSO profiles match display name {0}:\n'
|
||||
MULTIPLE_YUBIKEYS_CONNECTED = 'Multiple YubiKeys connected. Specify yubikey_serial_number and one of {0}\n'
|
||||
MUST_BE_NUMERIC = 'Must be numeric'
|
||||
NEED_READ_ACCESS = 'Need Read access'
|
||||
NEED_READ_WRITE_ACCESS = 'Need Read/Write access'
|
||||
NEED_WRITE_ACCESS = 'Need Write access'
|
||||
NESTED_LOOP_CMD_NOT_ALLOWED = 'Command can not be nested.'
|
||||
NEWUSER_REQUIREMENTS = 'newuser option requires: at least 1 recipient and givenname, familyname and password options'
|
||||
NEW_OWNER_MUST_DIFFER_FROM_OLD_OWNER = 'New owner must differ from old owner'
|
||||
NO_DATA = 'No data'
|
||||
NON_BLANK = 'Non-blank'
|
||||
NON_EMPTY = 'Non-empty'
|
||||
NOT_A = 'Not a'
|
||||
NOT_A_PRIMARY_EMAIL_ADDRESS = 'Not a primary email address'
|
||||
NOT_A_MEMBER = 'Not a member'
|
||||
NOT_ACTIVE = 'Not Active'
|
||||
NOT_ALLOWED = 'Not Allowed'
|
||||
NOT_AN_ENTITY = 'Not a {0}'
|
||||
NOT_APPROPRIATE = 'Not Appropriate'
|
||||
NOT_COMPATIBLE = 'Not Compatible'
|
||||
NOT_COPYABLE = 'Not Copyable'
|
||||
NOT_COPYABLE_INTO_ITSELF = 'Not copyable into itself'
|
||||
NOT_COPYABLE_SAME_NAME_CURRENT_FOLDER_MERGE = 'Not copyable with same name into current folder with duplicatefolders merge'
|
||||
NOT_COPYABLE_SAME_NAME_CURRENT_FOLDER_OVERWRITE = 'Not copyable with same name into current folder with duplicatefiles overwriteall|overwriteolder'
|
||||
NOT_DELETABLE = 'Not Deletable'
|
||||
NOT_FOUND = 'Not Found'
|
||||
NOT_MOVABLE = 'Not Movable'
|
||||
NOT_MOVABLE_IN_TRASH = 'Not Movable, in Trash'
|
||||
NOT_MOVABLE_INTO_ITSELF = 'Not movable into itself'
|
||||
NOT_MOVABLE_SAME_NAME_CURRENT_FOLDER_MERGE = 'Not movable with same name into current folder with duplicatefolders merge'
|
||||
NOT_MOVABLE_SAME_NAME_CURRENT_FOLDER_OVERWRITE = 'Not movable with same name into current folder with duplicatefiles overwriteall|overwriteolder'
|
||||
NOT_OWNED_BY = 'Not owned by {0}'
|
||||
NOT_SELECTED = 'Not Selected'
|
||||
NOT_WRITABLE = 'Not Writable'
|
||||
NOW_THE_PRIMARY_DOMAIN = 'Now the primary domain'
|
||||
NO_ACTION_SPECIFIED = 'No action specified'
|
||||
NO_AVAILABLE_LICENSES = "There aren't enough available licenses for the specified product-SKU pair(s)"
|
||||
NO_CHANGES = 'No changes'
|
||||
NO_CLIENT_ACCESS_ALLOWED = 'No Client Access allowed'
|
||||
NO_CLIENT_ACCESS_CREATE_UPDATE_ALLOWED = 'No Client Access create/update allowed'
|
||||
NO_COLUMNS_SELECTED_WITH_CSV_OUTPUT_HEADER_FILTER = 'No columns selected with {0} and {1}'
|
||||
NO_CREDENTIALS_REPLACEMENT = '{0}: {1} has {2} {3}. We only replace if there are 2.\n'
|
||||
NO_CSV_DATA_TO_UPLOAD = 'No CSV data to upload'
|
||||
NO_CSV_FILE_DATA_FOUND = 'No CSV file data found'
|
||||
NO_CSV_FILE_DATA_SAVED = 'No CSV file data saved'
|
||||
NO_CSV_FILE_SUBKEYS_SAVED = 'No CSV file subkeys saved'
|
||||
NO_DATA_TRANSFER_APP_FOR_PARAMETER = 'No data transfer application for key {0}'
|
||||
NO_ENTITIES_FOUND = 'No {0} found'
|
||||
NO_ENTITIES_MATCHED = 'No {0} matched'
|
||||
NO_FILTER_ACTIONS = 'No {0} actions specified'
|
||||
NO_FILTER_CRITERIA = 'No {0} criteria specified'
|
||||
NO_LABELS_MATCH = 'No Labels match'
|
||||
NO_LABELS_TO_PROCESS = 'No Labels to process'
|
||||
NO_MESSAGES_WITH_LABEL = 'No Messages with Label'
|
||||
NO_PARENTS_TO_CONVERT_TO_SHORTCUTS = 'No parents to convert to shortcuts'
|
||||
NO_REPORT_AVAILABLE = 'No {0} report available.'
|
||||
NO_SCOPES_FOR_API = 'There are no scopes authorized for the API(s): {0}'
|
||||
NO_SERIAL_NUMBERS_SPECIFIED = 'No serial numbers specified'
|
||||
NO_SSO_PROFILE_MATCHES = 'No SSO profile matches display name {0}'
|
||||
NO_SSO_PROFILE_ASSIGNED = 'No SSO profile assigned to {0} {1}'
|
||||
NO_SVCACCT_ACCESS_ALLOWED = 'No Service Account Access allowed'
|
||||
NO_TRANSFER_LACK_OF_DISK_SPACE = 'Transfer not performed due to lack of target drive space.'
|
||||
NO_USAGE_PARAMETERS_DATA_AVAILABLE = 'No usage parameters data available.'
|
||||
NO_USER_COUNTS_DATA_AVAILABLE = 'No User counts data available.'
|
||||
NUM_SELECTED_CLIENT_SCOPES = '\n{0} scopes are selected, if more than {1} scopes are selected, Google will probably generate a "Something went wrong" error\n'
|
||||
OAUTH2_GO_TO_LINK_MESSAGE = """
|
||||
Go to the following link in a browser on this computer or on another computer:
|
||||
|
||||
{url}
|
||||
|
||||
If you use a browser on another computer, you will get a browser error that the site can't be reached AFTER you
|
||||
click the Allow button, paste "Unable to connect" URL from other computer (only URL data up to &scope required):
|
||||
"""
|
||||
ON_CURRENT_PRIVATE_KEY = ' on current key'
|
||||
ON_VAULT_HOLD = 'On Google Vault Hold'
|
||||
ONLY_ADMINISTRATORS_CAN_PERFORM_SHARED_DRIVE_QUERIES = 'Only administrators can perform Shared Drive queries'
|
||||
ONLY_ADMINISTRATORS_CAN_SPECIFY_SHARED_DRIVE_ORGUNIT = 'Only administrators can specify Shared Drive Org Unit'
|
||||
ONLY_ONE_DEVICE_SELECTION_ALLOWED = 'Only one device selection allowed, filter = "{0}"'
|
||||
ONLY_ONE_JSON_RANGE_ALLOWED = 'Only one range/json allowed'
|
||||
ONLY_ONE_OWNER_ALLOWED = 'Only one owner allowed'
|
||||
OR = 'or'
|
||||
OU_AND_MOVETOOU_CANNOT_BE_IDENTICAL = 'ou {0} can not be be identical to movetoou {1}'
|
||||
OU_SUBOUS_CANNOT_BE_MOVED_TO_MOVETOOU = 'ou {0} sub OUs can not be be moved to movetoou {1}'
|
||||
PERMISSION_DENIED = 'The caller does not have permission'
|
||||
PLEASE_CORRECT_YOUR_SYSTEM_TIME = 'Please correct your system time.'
|
||||
PLEASE_ENTER_A_OR_M = 'Please enter a or m ...\n'
|
||||
PLEASE_SELECT_ENTITY_TO_PROCESS = '{0} {1} found, please select the correct one to {2} and specify with {3}'
|
||||
PLEASE_SPECIFY_BUILDING_EXACT_CASE_NAME_OR_ID = 'Please specify building by exact case name or ID.'
|
||||
PREVIEW_ONLY = 'Preview Only'
|
||||
PRIMARY_EMAIL_DID_NOT_MATCH_PATTERN = 'primaryEmail address did not match pattern: {0}'
|
||||
PROCESS = 'process'
|
||||
PROCESSES = 'processes'
|
||||
PROCESSING_ITEM_N = '{0},0,Processing item {1}\n'
|
||||
PROCESSING_ITEM_N_OF_M = '{0},0,Processing item {1}/{2}\n'
|
||||
PROFILE_PHOTO_NOT_FOUND = 'Profile photo not found'
|
||||
PROFILE_PHOTO_IS_DEFAULT = 'Profile photo is default'
|
||||
QUOTA_EXCEEDED = 'Quota exceeded'
|
||||
REASON_ONLY_VALID_WITH_CONTENTRESTRICTIONS_READONLY_TRUE = 'reason only valid with contentrestrictions readonly true'
|
||||
REAUTHENTICATION_IS_NEEDED = 'Reauthentication is needed, please run\n\ngam oauth create'
|
||||
RECOMMEND_RUNNING_GAM_ROTATE_SAKEY = 'Recommend running "gam rotate sakey" to get a new key\n'
|
||||
REFUSING_TO_DEPROVISION_DEVICES = 'Refusing to deprovision {0} devices because acknowledge_device_touch_requirement not specified.\nDeprovisioning a device means the device will have to be physically wiped and re-enrolled to be managed by your domain again.\nThis requires physical access to the device and is very time consuming to perform for each device.\nPlease add "acknowledge_device_touch_requirement" to the GAM command if you understand this and wish to proceed with the deprovision.\nPlease also be aware that deprovisioning can have an effect on your device license count.\nSee https://support.google.com/chrome/a/answer/3523633 for full details.'
|
||||
REFUSING_TO_DEPROVISION_N_DEVICES = 'Refusing to deprovision {0} devices due to maxtodepov {1}.\nSpecify "maxtodeprov 0" to deprovision all {0} devices'
|
||||
REPLY_TO_CUSTOM_REQUIRES_EMAIL_ADDRESS = 'replyto REPLY_TO_CUSTOM requires customReplyTo <EmailAddress>'
|
||||
REQUEST_COMPLETED_NO_FILES = 'Request completed but no results/files were returned, try requesting again'
|
||||
REQUEST_NOT_COMPLETE = 'Request needs to be completed before downloading, current status is: {0}'
|
||||
RERUN_THE_COMMAND_AND_SPECIFY_A_NEW_SANAME = """
|
||||
Re-run the command specify a new service account name with: saname <ServiceAccountName>
|
||||
See: https://github.com/GAM-team/GAM/wiki/Authorization#advanced-use
|
||||
"""
|
||||
RESOURCE_CAPACITY_FLOOR_REQUIRED = 'Options "capacity <Number>" (<Number> > 0) and "floor <String>" required'
|
||||
RESOURCE_FLOOR_REQUIRED = 'Option "floor <String>" required'
|
||||
RESULTS_TOO_LARGE_FOR_GOOGLE_SPREADSHEET = 'Results are too large for Google Spreadsheets. Uploading as a regular CSV file.'
|
||||
RETRIES_EXHAUSTED = 'Retries {0} exhausted'
|
||||
RETRYING_GOOGLE_SHEET_EXPORT_SLEEPING = 'Retrying Google Sheet export {0}/{1}. Sleeping {2} seconds\n'
|
||||
ROLE_MUST_BE_ORGANIZER = 'Role must be organizer'
|
||||
ROLE_NOT_IN_SET = 'Role not in set: {0})'
|
||||
SCHEMA_WOULD_HAVE_NO_FIELDS = '{0} would have no {1}'
|
||||
SELECTED = 'Selected'
|
||||
SERVICE_NOT_APPLICABLE = 'Service not applicable/Does not exist'
|
||||
SERVICE_NOT_APPLICABLE_THIS_ADDRESS = 'Service not applicable for this address: {0}'
|
||||
SERVICE_NOT_ENABLED = '{0} Service/App not enabled'
|
||||
SHORTCUT_TARGET_CAPABILITY_IS_FALSE = '{0} capability {1} is False'
|
||||
SITES_COMMAND_DEPRECATED = 'The Classic Sites API is deprecated, this command will not work:\n{0}'
|
||||
SKU_HAS_NO_MATCHING_ARCHIVED_USER_SKU = 'SKU {0} has no matching Archived User SKU'
|
||||
STARTING_THREAD = 'Starting thread'
|
||||
STATISTICS_COPY_FILE = 'Total: {0}, Copied: {1}, Shortcut created {2}, Shortcut exists {3}, Duplicate: {4}, Copy Failed: {5}, Not copyable: {6}, In skipids: {7}, Permissions Failed: {8}, Protected Ranges Failed: {9}'
|
||||
STATISTICS_COPY_FOLDER = 'Total: {0}, Copied: {1}, Shortcut created {2}, Shortcut exists {3}, Duplicate: {4}, Merged: {5}, Copy Failed: {6}, Not writable: {7}, Permissions Failed: {8}'
|
||||
STATISTICS_MOVE_FILE = 'Total: {0}, Moved: {1}, Shortcut created {2}, Shortcut exists {3}, Duplicate: {4}, Move Failed: {5}, Not movable: {6}'
|
||||
STATISTICS_MOVE_FOLDER = 'Total: {0}, Moved: {1}, Shortcut created {2}, Shortcut exists {3}, Duplicate: {4}, Merged: {5}, Move Failed: {6}, Not writable: {7}'
|
||||
STATISTICS_USER_NOT_ORGANIZER = 'User not organizer: {0}'
|
||||
STRING_LENGTH = 'string length'
|
||||
STUDENT_NOT_IN_COURSE = 'Student not in course'
|
||||
SUBKEY_FIELD_MISMATCH = 'subkeyfield {0} does not match saved subkeyfield {1}'
|
||||
SUBSCRIPTION_NOT_FOUND = 'Could not find subscription'
|
||||
SUFFIX_NOT_ALLOWED_WITH_CUSTOMLANGUAGE = 'Suffix {0} not allowed with customLanguage {1}'
|
||||
TASKLIST_TITLE_NOT_FOUND = 'Task list title not found'
|
||||
THREAD = 'thread'
|
||||
THREADS = 'threads'
|
||||
TO = 'To'
|
||||
TO_LC = 'to'
|
||||
TO_MAXIMUM_OF = 'to maximum of'
|
||||
TO_SET_UP_GOOGLE_CHAT = """
|
||||
To set up Google Chat for your current project, please go to:
|
||||
|
||||
{0}
|
||||
|
||||
and follow the instructions at:
|
||||
|
||||
https://github.com/GAM-team/GAM/wiki/Chat-Bot-Setup-Use#set-up-a-chat-bot
|
||||
|
||||
You'll use projects/{1}/topics/no-topic in Connection settings Cloud Pub/Sub Topic Name
|
||||
"""
|
||||
TOTAL_ITEMS_IN_ENTITY = 'Total {0} in {1}'
|
||||
TRIMMED_MESSAGE_FROM_LENGTH_TO_MAXIMUM = 'Trimmed message of length {0} to maximum length {1}'
|
||||
UNABLE_TO_GET_PERMISSION_ID = 'Unable to get Permission ID for <{0}>'
|
||||
UNABLE_TO_CREATE_NOT_FOUND_USER = 'Unable to create not found user, some required field (givenName, familyName, password/notfoundpassword) not present'
|
||||
UNAVAILABLE = 'Unavailable'
|
||||
UNKNOWN = 'Unknown'
|
||||
UNKNOWN_API_OR_VERSION = 'Unknown Google API or version: ({0}), contact {1}'
|
||||
UNRECOVERABLE_ERROR = 'Unrecoverable error'
|
||||
UPDATE_ATTENDEE_CHANGES = 'Update attendee changes'
|
||||
UPDATE_GAM_TO_64BIT = "You're running a 32-bit version of GAM on a 64-bit version of Windows, upgrade to a windows-x86_64 version of GAM"
|
||||
UPDATE_PRIMARY_EMAIL_PREVIEW = 'updateprimaryemail preview: {0}'
|
||||
UPDATE_USER_PASSWORD_CHANGE_NOTIFY_MESSAGE = 'The account password for #givenname# #familyname#, #user# has been changed to: #password#\n'
|
||||
UPDATE_USER_PASSWORD_CHANGE_NOTIFY_SUBJECT = 'Account #user# password has been changed'
|
||||
UPLOAD_CSV_FILE_INTERNAL_ERROR = 'Google reported "{0}" but the file was probably uploaded, check that it has {1} rows'
|
||||
UPLOADING_NEW_PUBLIC_CERTIFICATE_TO_GOOGLE = 'Uploading new public certificate to Google...\n'
|
||||
URL_ERROR = 'URL error: {0}'
|
||||
USE_MIMETYPE_TO_SPECIFY_GOOGLE_FORMAT = 'Use "mimetype <MimeType>" to specify Google file format\n'
|
||||
USED = 'Used'
|
||||
USER_BELONGS_TO_N_GROUPS_THAT_MAP_TO_ORGUNITS = 'User belongs to {0} groups ({1}) that map to OUs'
|
||||
USER_CANCELLED = 'User cancelled'
|
||||
USER_HAS_MULTIPLE_DIRECT_OR_INHERITED_MEMBERSHIPS_IN_GROUP = 'User has multiple direct or inherited memberships in group'
|
||||
USER_IN_OTHER_DOMAIN = '{0}: {1} in other domain.'
|
||||
USER_IS_NOT_ORGANIZER = 'User is not organizer, use anyorganizer option to override'
|
||||
USER_NOT_IN_MATCHUSERS = 'User not in matchusers'
|
||||
USER_SUBS_NOT_ALLOWED_TAG_REPLACEMENT = 'user substitutions not allowed in replace <Tag> <String>'
|
||||
USE_DOIT_ARGUMENT_TO_PERFORM_ACTION = 'Use the "doit" argument to perform action'
|
||||
USING_N_PROCESSES = '{0},0/{1},Using {2} {3}...\n'
|
||||
VALUES_ARE_NOT_CONSISTENT = 'Values are not consistent'
|
||||
VERSION_UPDATE_AVAILABLE = 'Version update available'
|
||||
WAITING_FOR_DATA_TRANSFER_TO_COMPLETE_SLEEPING = 'Waiting for Data Transfer to complete. Sleeping {0} seconds\n'
|
||||
WAITING_FOR_ITEM_CREATION_TO_COMPLETE_SLEEPING = 'Waiting for {0} creation to complete. Sleeping {1} seconds\n'
|
||||
WHAT_IS_YOUR_PROJECT_ID = '\nWhat is your project ID? '
|
||||
WILL_RERUN_WITH_NO_BROWSER_TRUE = 'Will re-run command with no_browser true\n'
|
||||
WITH = 'with'
|
||||
WOULD_MAKE_MEMBERSHIP_CYCLE = 'Would make membership cycle'
|
||||
WRITER_ACCESS_REQUIRED_TO_BOTH_CALENDARS = 'Writer access required to both calendars'
|
||||
WROTE_PRIVATE_KEY_DATA = 'Wrote private key data to {0}\n'
|
||||
WROTE_PUBLIC_CERTIFICATE = 'Wrote public certificate to {0}\n'
|
||||
YOU_CAN_ADD_DOMAIN_TO_ACCOUNT = 'You can now add: {0} or its subdomains as secondary or domain aliases of the Google Workspace Account: {1}'
|
||||
YUBIKEY_GENERATING_NONEXPORTABLE_PRIVATE_KEY = 'YubiKey is generating a non-exportable private key...\n'
|
||||
YUBIKEY_PIN_SET_TO = 'YubiKey PIN set to: {0}\n'
|
||||
@@ -1,262 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2026 Ross Scroggs All Rights Reserved.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Google SKUs
|
||||
|
||||
"""
|
||||
|
||||
# Products/SKUs
|
||||
_PRODUCTS = {
|
||||
'101001': 'Cloud Identity Free',
|
||||
'101005': 'Cloud Identity Premium',
|
||||
'101031': 'Google Workspace for Education',
|
||||
'101033': 'Google Voice',
|
||||
'101034': 'Google Workspace Archived User',
|
||||
'101035': 'Cloud Search',
|
||||
'101036': 'Google Meet Global Dialing',
|
||||
'101037': 'Google Workspace for Education',
|
||||
'101038': 'AppSheet',
|
||||
'101039': 'Assured Controls',
|
||||
'101040': 'Chrome Enterprise',
|
||||
'101043': 'Google Workspace Additional Storage',
|
||||
'101047': 'Gemini',
|
||||
'101049': 'Education Endpoint Management',
|
||||
'101050': 'Colab',
|
||||
'Google-Apps': 'Google Workspace',
|
||||
'Google-Chrome-Device-Management': 'Google Chrome Device Management',
|
||||
'Google-Drive-storage': 'Google Drive Storage',
|
||||
'Google-Vault': 'Google Vault',
|
||||
}
|
||||
_SKUS = {
|
||||
'1010010001': {
|
||||
'product': '101001', 'aliases': ['identity', 'cloudidentity', 'cloudidentityfree'], 'displayName': 'Cloud Identity Free'},
|
||||
'1010050001': {
|
||||
'product': '101005', 'aliases': ['identitypremium', 'cloudidentitypremium'], 'displayName': 'Cloud Identity Premium'},
|
||||
'1010070001': {
|
||||
'product': 'Google-Apps', 'aliases': ['gwef', 'workspaceeducationfundamentals'], 'displayName': 'Google Workspace for Education Fundamentals'},
|
||||
'1010070004': {
|
||||
'product': 'Google-Apps', 'aliases': ['gwegmo', 'workspaceeducationgmailonly'], 'displayName': 'Google Workspace for Education Gmail Only'},
|
||||
'1010310002': {
|
||||
'product': '101031', 'aliases': ['gsefe', 'e4e', 'gsuiteenterpriseeducation'], 'displayName': 'Google Workspace for Education Plus - Legacy'},
|
||||
'1010310003': {
|
||||
'product': '101031', 'aliases': ['gsefes', 'e4es', 'gsuiteenterpriseeducationstudent'], 'displayName': 'Google Workspace for Education Plus - Legacy (Student)'},
|
||||
'1010310005': {
|
||||
'product': '101031', 'aliases': ['gwes', 'workspaceeducationstandard'], 'displayName': 'Google Workspace for Education Standard'},
|
||||
'1010310006': {
|
||||
'product': '101031', 'aliases': ['gwesstaff', 'workspaceeducationstandardstaff'], 'displayName': 'Google Workspace for Education Standard (Staff)'},
|
||||
'1010310007': {
|
||||
'product': '101031', 'aliases': ['gwesstudent', 'workspaceeducationstandardstudent'], 'displayName': 'Google Workspace for Education Standard (Extra Student)'},
|
||||
'1010310008': {
|
||||
'product': '101031', 'aliases': ['gwep', 'workspaceeducationplus'], 'displayName': 'Google Workspace for Education Plus'},
|
||||
'1010310009': {
|
||||
'product': '101031', 'aliases': ['gwepstaff', 'workspaceeducationplusstaff'], 'displayName': 'Google Workspace for Education Plus (Staff)'},
|
||||
'1010310010': {
|
||||
'product': '101031', 'aliases': ['gwepstudent', 'workspaceeducationplusstudent'], 'displayName': 'Google Workspace for Education Plus (Extra Student)'},
|
||||
'1010330002': {
|
||||
'product': '101033', 'aliases': ['gvpremier', 'voicepremier', 'googlevoicepremier'], 'displayName': 'Google Voice Premier'},
|
||||
'1010330003': {
|
||||
'product': '101033', 'aliases': ['gvstarter', 'voicestarter', 'googlevoicestarter'], 'displayName': 'Google Voice Starter'},
|
||||
'1010330004': {
|
||||
'product': '101033', 'aliases': ['gvstandard', 'voicestandard', 'googlevoicestandard'], 'displayName': 'Google Voice Standard'},
|
||||
'1010350001': {
|
||||
'product': '101035', 'aliases': ['cloudsearch'], 'displayName': 'Cloud Search'},
|
||||
'1010360001': {
|
||||
'product': '101036', 'aliases': ['meetdialing','googlemeetglobaldialing'], 'displayName': 'Google Meet Global Dialing'},
|
||||
'1010370001': {
|
||||
'product': '101037', 'aliases': ['gwetlu', 'workspaceeducationupgrade'], 'displayName': 'Google Workspace for Education: Teaching and Learning Upgrade'},
|
||||
'1010380001': {
|
||||
'product': '101038', 'aliases': ['appsheetcore'], 'displayName': 'AppSheet Core'},
|
||||
'1010380002': {
|
||||
'product': '101038', 'aliases': ['appsheetstandard', 'appsheetenterprisestandard'], 'displayName': 'AppSheet Enterprise Standard'},
|
||||
'1010380003': {
|
||||
'product': '101038', 'aliases': ['appsheetplus', 'appsheetenterpriseplus'], 'displayName': 'AppSheet Enterprise Plus'},
|
||||
'1010390001': {
|
||||
'product': '101039', 'aliases': ['assuredcontrols'], 'displayName': 'Assured Controls'},
|
||||
'1010390002': {
|
||||
'product': '101039', 'aliases': ['assuredcontrolsplus'], 'displayName': 'Assured Controls Plus'},
|
||||
'1010400001': {
|
||||
'product': '101040', 'aliases': ['beyondcorp', 'beyondcorpenterprise', 'bce', 'cep', 'chromeenterprisepremium'], 'displayName': 'Chrome Enterprise Premium'},
|
||||
'1010430001': {
|
||||
'product': '101043', 'aliases': ['gwas', 'plusstorage'], 'displayName': 'Google Workspace Additional Storage'},
|
||||
'1010470001': {
|
||||
'product': '101047', 'aliases': ['geminient', 'duetai'], 'displayName': 'Gemini Enterprise - Legacy'},
|
||||
'1010470002': {
|
||||
'product': '101047', 'aliases': ['gwlabs', 'workspacelabs'], 'displayName': 'Google Workspace Labs'},
|
||||
'1010470003': {
|
||||
'product': '101047', 'aliases': ['geminibiz'], 'displayName': 'Gemini Business'},
|
||||
'1010470004': {
|
||||
'product': '101047', 'aliases': ['gaiproedu', 'geminiedu'], 'displayName': 'Google AI Pro for Education'},
|
||||
'1010470005': {
|
||||
'product': '101047', 'aliases': ['geminiedupremium'], 'displayName': 'Gemini Education Premium'},
|
||||
'1010470006': {
|
||||
'product': '101047', 'aliases': ['aisecurity'], 'displayName': 'AI Security'},
|
||||
'1010470007': {
|
||||
'product': '101047', 'aliases': ['aimeetingsandmessaging'], 'displayName': 'AI Meetings and Messaging'},
|
||||
'1010470008': {
|
||||
'product': '101047', 'aliases': ['geminiultra'], 'displayName': 'Google AI Ultra for Business'},
|
||||
'1010470009': {
|
||||
'product': '101047', 'aliases': ['aiexpandedaccess'], 'displayName': 'AI Expanded Access'},
|
||||
'1010490001': {
|
||||
'product': '101049', 'aliases': ['eeu'], 'displayName': 'Endpoint Education Upgrade'},
|
||||
'1010500001': {
|
||||
'product': '101050', 'aliases': ['colabpro'], 'displayName': 'Colab Pro'},
|
||||
'1010500002': {
|
||||
'product': '101050', 'aliases': ['colabpro+', 'colabproplus'], 'displayName': 'Colab Pro+'},
|
||||
'Google-Apps': {
|
||||
'product': 'Google-Apps', 'aliases': ['standard', 'free'], 'displayName': 'G Suite Legacy'},
|
||||
'Google-Apps-For-Business': {
|
||||
'product': 'Google-Apps', 'aliases': ['gafb', 'gafw', 'basic', 'gsuitebasic'], 'displayName': 'G Suite Basic'},
|
||||
'Google-Apps-For-Education': {
|
||||
'product': 'Google-Apps', 'aliases': ['gafe', 'gsuiteeducation', 'gsuiteedu'], 'displayName': 'Google Workspace for Education - Fundamentals'},
|
||||
'Google-Apps-For-Government': {
|
||||
'product': 'Google-Apps', 'aliases': ['gafg', 'gsuitegovernment', 'gsuitegov'], 'displayName': 'Google Workspace Government'},
|
||||
'Google-Apps-For-Postini': {
|
||||
'product': 'Google-Apps', 'aliases': ['gams', 'postini', 'gsuitegams', 'gsuitepostini', 'gsuitemessagesecurity'], 'displayName': 'Google Apps Message Security'},
|
||||
'Google-Apps-Lite': {
|
||||
'product': 'Google-Apps', 'aliases': ['gal', 'gsl', 'lite', 'gsuitelite'], 'displayName': 'G Suite Lite'},
|
||||
'Google-Apps-Unlimited': {
|
||||
'product': 'Google-Apps', 'aliases': ['gau', 'gsb', 'unlimited', 'gsuitebusiness'], 'displayName': 'G Suite Business'},
|
||||
'1010020020': {
|
||||
'product': 'Google-Apps', 'aliases': ['gae', 'gse', 'enterprise', 'gsuiteenterprise',
|
||||
'wsentplus', 'workspaceenterpriseplus'], 'displayName': 'Google Workspace Enterprise Plus (formerly G Suite Enterprise)'},
|
||||
'1010020025': {
|
||||
'product': 'Google-Apps', 'aliases': ['wsbizplus', 'workspacebusinessplus'], 'displayName': 'Google Workspace Business Plus'},
|
||||
'1010020026': {
|
||||
'product': 'Google-Apps', 'aliases': ['wsentstan', 'workspaceenterprisestandard'], 'displayName': 'Google Workspace Enterprise Standard'},
|
||||
'1010020027': {
|
||||
'product': 'Google-Apps', 'aliases': ['wsbizstart', 'wsbizstarter', 'workspacebusinessstarter'], 'displayName': 'Google Workspace Business Starter'},
|
||||
'1010020028': {
|
||||
'product': 'Google-Apps', 'aliases': ['wsbizstan', 'workspacebusinessstandard'], 'displayName': 'Google Workspace Business Standard'},
|
||||
'1010020029': {
|
||||
'product': 'Google-Apps', 'aliases': ['wes', 'wsentstarter', 'workspaceenterprisestarter'], 'displayName': 'Workspace Enterprise Starter'},
|
||||
'1010020030': {
|
||||
'product': 'Google-Apps', 'aliases': ['wsflw', 'workspacefrontline', 'workspacefrontlineworker'], 'displayName': 'Google Workspace Frontline Starter'},
|
||||
'1010020031': {
|
||||
'product': 'Google-Apps', 'aliases': ['wsflwstan', 'workspacefrontlinestan', 'workspacefrontlineworkerstan'], 'displayName': 'Google Workspace Frontline Standard'},
|
||||
'1010020034': {
|
||||
'product': 'Google-Apps', 'aliases': ['wsflwplus', 'workspacefrontlineplus', 'workspacefrontlineworkerplus'], 'displayName': 'Google Workspace Frontline Plus'},
|
||||
'1010340001': {
|
||||
'product': '101034', 'aliases': ['gseau', 'enterprisearchived', 'gsuiteenterprisearchived'], 'displayName': 'Google Workspace Enterprise Plus - Archived User'},
|
||||
'1010340002': {
|
||||
'product': '101034', 'aliases': ['gsbau', 'businessarchived', 'gsuitebusinessarchived'], 'displayName': 'Google Workspace Business - Archived User'},
|
||||
'1010340003': {
|
||||
'product': '101034', 'aliases': ['wsbizplusarchived', 'workspacebusinessplusarchived'], 'displayName': 'Google Workspace Business Plus - Archived User'},
|
||||
'1010340004': {
|
||||
'product': '101034', 'aliases': ['wsentstanarchived', 'workspaceenterprisestandardarchived'], 'displayName': 'Google Workspace Enterprise Standard - Archived User'},
|
||||
'1010340005': {
|
||||
'product': '101034', 'aliases': ['wsbizstarterarchived', 'workspacebusinessstarterarchived'], 'displayName': 'Google Workspace Business Starter - Archived User'},
|
||||
'1010340006': {
|
||||
'product': '101034', 'aliases': ['wsbizstanarchived', 'workspacebusinessstanarchived'], 'displayName': 'Google Workspace Business Standard - Archived User'},
|
||||
'1010340007': {
|
||||
'product': '101034', 'aliases': ['gwefau', 'gwefarchived', 'workspaceeducationfundamentalsarchived'], 'displayName': 'Google Workspace for Education Fundamentals - Archived User'},
|
||||
'1010060001': {
|
||||
'product': '101006', 'aliases': ['gsuiteessentials', 'essentials',
|
||||
'd4e', 'driveenterprise', 'drive4enterprise',
|
||||
'wsess', 'workspaceesentials'], 'displayName': 'Google Workspace Essentials (formerly G Suite Essentials)'},
|
||||
'1010060003': {
|
||||
'product': 'Google-Apps', 'aliases': ['wsentess', 'workspaceenterpriseessentials'], 'displayName': 'Google Workspace Enterprise Essentials'},
|
||||
'1010060005': {
|
||||
'product': 'Google-Apps', 'aliases': ['wsessplus', 'workspaceessentialsplus'], 'displayName': 'Google Workspace Enterprise Essentials Plus'},
|
||||
'Google-Drive-storage-20GB': {
|
||||
'product': 'Google-Drive-storage', 'aliases': ['drive20gb', '20gb', 'googledrivestorage20gb'], 'displayName': 'Google Drive Storage 20GB'},
|
||||
'Google-Drive-storage-50GB': {
|
||||
'product': 'Google-Drive-storage', 'aliases': ['drive50gb', '50gb', 'googledrivestorage50gb'], 'displayName': 'Google Drive Storage 50GB'},
|
||||
'Google-Drive-storage-200GB': {
|
||||
'product': 'Google-Drive-storage', 'aliases': ['drive200gb', '200gb', 'googledrivestorage200gb'], 'displayName': 'Google Drive Storage 200GB'},
|
||||
'Google-Drive-storage-400GB': {
|
||||
'product': 'Google-Drive-storage', 'aliases': ['drive400gb', '400gb', 'googledrivestorage400gb'], 'displayName': 'Google Drive Storage 400GB'},
|
||||
'Google-Drive-storage-1TB': {
|
||||
'product': 'Google-Drive-storage', 'aliases': ['drive1tb', '1tb', 'googledrivestorage1tb'], 'displayName': 'Google Drive Storage 1TB'},
|
||||
'Google-Drive-storage-2TB': {
|
||||
'product': 'Google-Drive-storage', 'aliases': ['drive2tb', '2tb', 'googledrivestorage2tb'], 'displayName': 'Google Drive Storage 2TB'},
|
||||
'Google-Drive-storage-4TB': {
|
||||
'product': 'Google-Drive-storage', 'aliases': ['drive4tb', '4tb', 'googledrivestorage4tb'], 'displayName': 'Google Drive Storage 4TB'},
|
||||
'Google-Drive-storage-8TB': {
|
||||
'product': 'Google-Drive-storage', 'aliases': ['drive8tb', '8tb', 'googledrivestorage8tb'], 'displayName': 'Google Drive Storage 8TB'},
|
||||
'Google-Drive-storage-16TB': {
|
||||
'product': 'Google-Drive-storage', 'aliases': ['drive16tb', '16tb', 'googledrivestorage16tb'], 'displayName': 'Google Drive Storage 16TB'},
|
||||
'Google-Vault': {
|
||||
'product': 'Google-Vault', 'aliases': ['vault', 'googlevault'], 'displayName': 'Google Vault'},
|
||||
'Google-Vault-Former-Employee': {
|
||||
'product': 'Google-Vault', 'aliases': ['vfe', 'googlevaultformeremployee'], 'displayName': 'Google Vault - Former Employee'},
|
||||
'Google-Chrome-Device-Management': {
|
||||
'product': 'Google-Chrome-Device-Management', 'aliases': ['chrome', 'cdm', 'googlechromedevicemanagement'], 'displayName': 'Google Chrome Device Management'}
|
||||
}
|
||||
|
||||
ARCHIVABLE_SKUS = {'1010020020', '1010020025', '1010020026', '1010020027', '1010020028', 'Google-Apps-Unlimited'}
|
||||
|
||||
def getProductAndSKU(sku):
|
||||
l_sku = sku.lower().replace('-', '').replace(' ', '').replace('"', '').replace("'", '').strip()
|
||||
if l_sku.startswith('nv:'):
|
||||
if ':' in sku[3:]:
|
||||
return sku[3:].split(':', 1)
|
||||
return (None, sku)
|
||||
for a_sku, sku_values in list(_SKUS.items()):
|
||||
if ((l_sku == a_sku.lower().replace('-', '')) or
|
||||
(l_sku in sku_values['aliases']) or
|
||||
(l_sku == sku_values['displayName'].lower().replace(' ', ''))):
|
||||
return (sku_values['product'], a_sku)
|
||||
return (None, sku)
|
||||
|
||||
def productIdToDisplayName(productId):
|
||||
return _PRODUCTS.get(productId, productId)
|
||||
|
||||
def formatProductIdDisplayName(productId):
|
||||
productIdDisplay = productIdToDisplayName(productId)
|
||||
if productId == productIdDisplay:
|
||||
return productId
|
||||
return f'{productId} ({productIdDisplay})'
|
||||
|
||||
def normalizeProductId(product):
|
||||
l_product = product.lower().replace('-', '').replace(' ', '').strip()
|
||||
if l_product.startswith('nv:'):
|
||||
return (True, product[3:])
|
||||
for a_sku, sku_values in list(_SKUS.items()):
|
||||
if ((l_product == sku_values['product'].lower().replace('-', '')) or
|
||||
(l_product == a_sku.lower().replace('-', '')) or
|
||||
(l_product in sku_values['aliases']) or
|
||||
(l_product == sku_values['displayName'].lower().replace(' ', ''))):
|
||||
return (True, sku_values['product'])
|
||||
return (False, product)
|
||||
|
||||
def getSortedProductList():
|
||||
return sorted(_PRODUCTS)
|
||||
|
||||
def skuIdToDisplayName(skuId):
|
||||
return _SKUS[skuId]['displayName'] if skuId in _SKUS else skuId
|
||||
|
||||
def formatSKUIdDisplayName(skuId):
|
||||
skuIdDisplay = skuIdToDisplayName(skuId)
|
||||
if skuId == skuIdDisplay:
|
||||
return skuId
|
||||
return f'{skuId} ({skuIdDisplay})'
|
||||
|
||||
def getSortedSKUList():
|
||||
return sorted(_SKUS)
|
||||
|
||||
def convertProductListToSKUList(productList):
|
||||
skuList = []
|
||||
for productId in productList:
|
||||
skuList += [(productId, skuId) for skuId in _SKUS if _SKUS[skuId]['product'] == productId]
|
||||
return skuList
|
||||
|
||||
def getAllSKUs():
|
||||
return convertProductListToSKUList(sorted(_PRODUCTS))
|
||||
|
||||
def getGSuiteSKUs():
|
||||
return convertProductListToSKUList(['Google-Apps', '101031'])
|
||||
@@ -1,287 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2026 Ross Scroggs All Rights Reserved.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""GAM user properties
|
||||
|
||||
"""
|
||||
|
||||
# notes
|
||||
# a|b|c
|
||||
# getKeywordAttribute(CUSTOM_TYPE_NOCUSTOM, attrdict)
|
||||
|
||||
#CUSTOM_TYPE_NOCUSTOM = {
|
||||
# PTKW_CL_TYPE_KEYWORD: 'type',
|
||||
# PTKW_CL_CUSTOM_KEYWORD: None,
|
||||
# PTKW_ATTR_TYPE_KEYWORD: 'type',
|
||||
# PTKW_ATTR_TYPE_CUSTOM_VALUE: None,
|
||||
# PTKW_ATTR_CUSTOMTYPE_KEYWORD: None,
|
||||
# PTKW_KEYWORD_LIST: ['a', 'b', 'c']
|
||||
# }
|
||||
|
||||
# addresses, ims
|
||||
# type a|b|c|([custom] <String>)
|
||||
# getChoice([CUSTOM_TYPE_CUSTOM[PTKW_CL_TYPE_KEYWORD]])
|
||||
# getKeywordAttribute(CUSTOM_TYPE_CUSTOM, attrdict)
|
||||
|
||||
# emails, externalids, relations, websites
|
||||
# [type] a|b|c|([custom] <String>)
|
||||
# getChoice([CUSTOM_TYPE_CUSTOM[PTKW_CL_TYPE_KEYWORD]], defaultChoice=None)
|
||||
# getKeywordAttribute(CUSTOM_TYPE_IMPLICIT, attrdict)
|
||||
|
||||
# locations, phones
|
||||
# type a|b|c|([custom] <String>)
|
||||
# if argument == CUSTOM_TYPE_CUSTOM[PTKW_CL_TYPE_KEYWORD]:
|
||||
# getKeywordAttribute(CUSTOM_TYPE_CUSTOM, attrdict)
|
||||
|
||||
#CUSTOM_TYPE_CUSTOM = {
|
||||
# PTKW_CL_TYPE_KEYWORD: 'type',
|
||||
# PTKW_CL_CUSTOM_KEYWORD: 'custom',
|
||||
# PTKW_ATTR_TYPE_KEYWORD: 'type',
|
||||
# PTKW_ATTR_TYPE_CUSTOM_VALUE: 'custom',
|
||||
# PTKW_ATTR_CUSTOMTYPE_KEYWORD: 'customType',
|
||||
# PTKW_KEYWORD_LIST: ['custom', 'a', 'b', 'c']
|
||||
# }
|
||||
|
||||
# organizations
|
||||
# (type a|b|c|([custom] <String>)) | (custom_type <String>)
|
||||
# if argument == CUSTOM_TYPE_DIFFERENT_KEYWORD[PTKW_CL_TYPE_KEYWORD]:
|
||||
# getKeywordAttribute(CUSTOM_TYPE_DIFFERENT_KEYWORD, attrdict)
|
||||
# elif argument == CUSTOM_TYPE_DIFFERENT_KEYWORD[PTKW_CL_CUSTOM_KEYWORD]:
|
||||
# attrdict[CUSTOM_TYPE_DIFFERENT_KEYWORD[PTKW_ATTR_CUSTOMTYPE_KEYWORD]] = getValue()
|
||||
|
||||
#CUSTOM_TYPE_DIFFERENT_KEYWORD = {
|
||||
# PTKW_CL_TYPE_KEYWORD: 'type',
|
||||
# PTKW_CL_CUSTOM_KEYWORD: 'custom',
|
||||
# PTKW_CL_CUSTOMTYPE_KEYWORD: 'custom_type',
|
||||
# PTKW_ATTR_TYPE_KEYWORD: 'type',
|
||||
# PTKW_ATTR_TYPE_CUSTOM_VALUE: 'custom',
|
||||
# PTKW_ATTR_CUSTOMTYPE_KEYWORD: 'customType',
|
||||
# PTKW_KEYWORD_LIST: ['custom', 'a', 'b', 'c']
|
||||
# }
|
||||
|
||||
# Keys into USER_PROPERTIES
|
||||
CLASS = 'clas'
|
||||
TITLE = 'titl'
|
||||
TYPE_KEYWORDS = 'tykw'
|
||||
PTKW_CL_TYPE_KEYWORD = 'ctkw'
|
||||
PTKW_CL_CUSTOM_KEYWORD = 'ccuk'
|
||||
PTKW_CL_CUSTOMTYPE_KEYWORD = 'cctk'
|
||||
PTKW_ATTR_TYPE_KEYWORD = 'atkw'
|
||||
PTKW_ATTR_TYPE_CUSTOM_VALUE = 'atcv'
|
||||
PTKW_ATTR_CUSTOMTYPE_KEYWORD = 'actk'
|
||||
PTKW_KEYWORD_LIST = 'kwli'
|
||||
#
|
||||
PC_ADDRESSES = 'addr'
|
||||
PC_ALIASES = 'alia'
|
||||
PC_ARRAY = 'arry'
|
||||
PC_BOOLEAN = 'bool'
|
||||
PC_EMAILS = 'emai'
|
||||
PC_GENDER = 'gndr'
|
||||
PC_IMS = 'ims '
|
||||
PC_LANGUAGES = 'lang'
|
||||
PC_LOCATIONS = 'loca'
|
||||
PC_NAME = 'name'
|
||||
PC_NOTES = 'note'
|
||||
PC_ORGANIZATIONS = 'orga'
|
||||
PC_POSIX = 'posi'
|
||||
PC_SCHEMAS = 'schm'
|
||||
PC_SSH = 'ssh '
|
||||
PC_STRING = 'stri'
|
||||
PC_TIME = 'time'
|
||||
|
||||
PROPERTIES = {
|
||||
'primaryEmail':
|
||||
{CLASS: PC_STRING, TITLE: 'User',},
|
||||
'name':
|
||||
{CLASS: PC_NAME, TITLE: 'Name',},
|
||||
'givenName':
|
||||
{CLASS: PC_STRING, TITLE: 'First Name',},
|
||||
'familyName':
|
||||
{CLASS: PC_STRING, TITLE: 'Last Name',},
|
||||
'fullName':
|
||||
{CLASS: PC_STRING, TITLE: 'Full Name',},
|
||||
'displayName':
|
||||
{CLASS: PC_STRING, TITLE: 'Display Name',},
|
||||
'primaryGuestEmail':
|
||||
{CLASS: PC_STRING, TITLE: 'Primary Guest Email',},
|
||||
'languages':
|
||||
{CLASS: PC_LANGUAGES, TITLE: 'Languages',},
|
||||
'languageCode':
|
||||
{CLASS: PC_LANGUAGES, TITLE: 'Languages',},
|
||||
'customLanguage':
|
||||
{CLASS: PC_LANGUAGES, TITLE: 'Custom Languages',},
|
||||
'password':
|
||||
{CLASS: PC_STRING, TITLE: 'Password',},
|
||||
'hashFunction':
|
||||
{CLASS: PC_STRING, TITLE: 'Hash Function',},
|
||||
'isAdmin':
|
||||
{CLASS: PC_BOOLEAN, TITLE: 'Is a Super Admin',},
|
||||
'isDelegatedAdmin':
|
||||
{CLASS: PC_BOOLEAN, TITLE: 'Is Delegated Admin',},
|
||||
'isGuestUser':
|
||||
{CLASS: PC_BOOLEAN, TITLE: 'Is a Guest User',},
|
||||
'isEnrolledIn2Sv':
|
||||
{CLASS: PC_BOOLEAN, TITLE: '2-step enrolled',},
|
||||
'isEnforcedIn2Sv':
|
||||
{CLASS: PC_BOOLEAN, TITLE: '2-step enforced',},
|
||||
'agreedToTerms':
|
||||
{CLASS: PC_BOOLEAN, TITLE: 'Has Agreed to Terms',},
|
||||
'ipWhitelisted':
|
||||
{CLASS: PC_BOOLEAN, TITLE: 'IP Whitelisted',},
|
||||
'archived':
|
||||
{CLASS: PC_BOOLEAN, TITLE: 'Is Archived',},
|
||||
'archivalTime':
|
||||
{CLASS: PC_TIME, TITLE: 'Archival Time',},
|
||||
'suspended':
|
||||
{CLASS: PC_BOOLEAN, TITLE: 'Account Suspended',},
|
||||
'suspensionReason':
|
||||
{CLASS: PC_STRING, TITLE: 'Suspension Reason',},
|
||||
'suspensionTime':
|
||||
{CLASS: PC_TIME, TITLE: 'Suspension Time',},
|
||||
'changePasswordAtNextLogin':
|
||||
{CLASS: PC_BOOLEAN, TITLE: 'Must Change Password',},
|
||||
'recoveryEmail':
|
||||
{CLASS: PC_STRING, TITLE: 'Recovery Email',},
|
||||
'recoveryPhone':
|
||||
{CLASS: PC_STRING, TITLE: 'Recovery Phone',},
|
||||
'id':
|
||||
{CLASS: PC_STRING, TITLE: 'Google Unique ID',},
|
||||
'customerId':
|
||||
{CLASS: PC_STRING, TITLE: 'Customer ID',},
|
||||
'isMailboxSetup':
|
||||
{CLASS: PC_BOOLEAN, TITLE: 'Mailbox is setup',},
|
||||
'includeInGlobalAddressList':
|
||||
{CLASS: PC_BOOLEAN, TITLE: 'Included in GAL',},
|
||||
'creationTime':
|
||||
{CLASS: PC_TIME, TITLE: 'Creation Time',},
|
||||
'lastLoginTime':
|
||||
{CLASS: PC_TIME, TITLE: 'Last login time',},
|
||||
'deletionTime':
|
||||
{CLASS: PC_TIME, TITLE: 'Deletion Time',},
|
||||
'orgUnitPath':
|
||||
{CLASS: PC_STRING, TITLE: 'Google Org Unit Path',},
|
||||
'thumbnailPhotoUrl':
|
||||
{CLASS: PC_STRING, TITLE: 'Photo URL',},
|
||||
'addresses':
|
||||
{CLASS: PC_ADDRESSES, TITLE: 'Addresses',
|
||||
TYPE_KEYWORDS:
|
||||
{PTKW_CL_TYPE_KEYWORD: 'type', PTKW_CL_CUSTOM_KEYWORD: 'custom',
|
||||
PTKW_ATTR_TYPE_KEYWORD: 'type', PTKW_ATTR_TYPE_CUSTOM_VALUE: 'custom', PTKW_ATTR_CUSTOMTYPE_KEYWORD: 'customType',
|
||||
PTKW_KEYWORD_LIST: ['custom', 'home', 'other', 'work'],},},
|
||||
'emails':
|
||||
{CLASS: PC_EMAILS, TITLE: 'Other Emails',
|
||||
TYPE_KEYWORDS:
|
||||
{PTKW_CL_TYPE_KEYWORD: 'type', PTKW_CL_CUSTOM_KEYWORD: 'custom',
|
||||
PTKW_ATTR_TYPE_KEYWORD: 'type', PTKW_ATTR_TYPE_CUSTOM_VALUE: 'custom', PTKW_ATTR_CUSTOMTYPE_KEYWORD: 'customType',
|
||||
PTKW_KEYWORD_LIST: ['custom', 'home', 'other', 'work'],},},
|
||||
'externalIds':
|
||||
{CLASS: PC_ARRAY, TITLE: 'External IDs',
|
||||
TYPE_KEYWORDS:
|
||||
{PTKW_CL_TYPE_KEYWORD: 'type', PTKW_CL_CUSTOM_KEYWORD: 'custom',
|
||||
PTKW_ATTR_TYPE_KEYWORD: 'type', PTKW_ATTR_TYPE_CUSTOM_VALUE: 'custom', PTKW_ATTR_CUSTOMTYPE_KEYWORD: 'customType',
|
||||
PTKW_KEYWORD_LIST: ['custom', 'account', 'customer', 'login_id', 'network', 'organization'],},},
|
||||
'gender':
|
||||
{CLASS: PC_GENDER, TITLE: 'Gender',
|
||||
TYPE_KEYWORDS:
|
||||
{PTKW_CL_TYPE_KEYWORD: 'type', PTKW_CL_CUSTOM_KEYWORD: 'other',
|
||||
PTKW_ATTR_TYPE_KEYWORD: 'type', PTKW_ATTR_TYPE_CUSTOM_VALUE: 'other', PTKW_ATTR_CUSTOMTYPE_KEYWORD: 'customGender',
|
||||
PTKW_KEYWORD_LIST: ['male', 'female', 'other', 'unknown'],},},
|
||||
'ims':
|
||||
{CLASS: PC_IMS, TITLE: 'IMs',
|
||||
TYPE_KEYWORDS:
|
||||
{PTKW_CL_TYPE_KEYWORD: 'type', PTKW_CL_CUSTOM_KEYWORD: 'custom',
|
||||
PTKW_ATTR_TYPE_KEYWORD: 'type', PTKW_ATTR_TYPE_CUSTOM_VALUE: 'custom', PTKW_ATTR_CUSTOMTYPE_KEYWORD: 'customType',
|
||||
PTKW_KEYWORD_LIST: ['custom', 'home', 'other', 'work'],},},
|
||||
'keywords':
|
||||
{CLASS: PC_ARRAY, TITLE: 'Keywords',
|
||||
TYPE_KEYWORDS:
|
||||
{PTKW_CL_TYPE_KEYWORD: 'type', PTKW_CL_CUSTOM_KEYWORD: 'custom',
|
||||
PTKW_ATTR_TYPE_KEYWORD: 'type', PTKW_ATTR_TYPE_CUSTOM_VALUE: 'custom', PTKW_ATTR_CUSTOMTYPE_KEYWORD: 'customType',
|
||||
PTKW_KEYWORD_LIST: ['custom', 'mission', 'occupation', 'outlook'],},},
|
||||
'locations':
|
||||
{CLASS: PC_LOCATIONS, TITLE: 'Locations',
|
||||
TYPE_KEYWORDS:
|
||||
{PTKW_CL_TYPE_KEYWORD: 'type', PTKW_CL_CUSTOM_KEYWORD: 'custom',
|
||||
PTKW_ATTR_TYPE_KEYWORD: 'type', PTKW_ATTR_TYPE_CUSTOM_VALUE: 'custom', PTKW_ATTR_CUSTOMTYPE_KEYWORD: 'customType',
|
||||
PTKW_KEYWORD_LIST: ['custom', 'default', 'desk'],},},
|
||||
'notes':
|
||||
{CLASS: PC_NOTES, TITLE: 'Notes',
|
||||
TYPE_KEYWORDS:
|
||||
{PTKW_CL_TYPE_KEYWORD: 'type', PTKW_CL_CUSTOM_KEYWORD: None,
|
||||
PTKW_ATTR_TYPE_KEYWORD: 'contentType', PTKW_ATTR_TYPE_CUSTOM_VALUE: None, PTKW_ATTR_CUSTOMTYPE_KEYWORD: None,
|
||||
PTKW_KEYWORD_LIST: ['text_plain', 'text_html'],},},
|
||||
'organizations':
|
||||
{CLASS: PC_ORGANIZATIONS, TITLE: 'Organizations',
|
||||
TYPE_KEYWORDS:
|
||||
{PTKW_CL_TYPE_KEYWORD: 'type', PTKW_CL_CUSTOM_KEYWORD: 'custom', PTKW_CL_CUSTOMTYPE_KEYWORD: 'customtype',
|
||||
PTKW_ATTR_TYPE_KEYWORD: 'type', PTKW_ATTR_TYPE_CUSTOM_VALUE: None, PTKW_ATTR_CUSTOMTYPE_KEYWORD: 'customType',
|
||||
PTKW_KEYWORD_LIST: ['custom', 'domain_only', 'school', 'unknown', 'work'],},},
|
||||
'phones':
|
||||
{CLASS: PC_ARRAY, TITLE: 'Phones',
|
||||
TYPE_KEYWORDS:
|
||||
{PTKW_CL_TYPE_KEYWORD: 'type', PTKW_CL_CUSTOM_KEYWORD: 'custom',
|
||||
PTKW_ATTR_TYPE_KEYWORD: 'type', PTKW_ATTR_TYPE_CUSTOM_VALUE: 'custom', PTKW_ATTR_CUSTOMTYPE_KEYWORD: 'customType',
|
||||
PTKW_KEYWORD_LIST: ['custom', 'home', 'work', 'other',
|
||||
'home_fax', 'work_fax', 'other_fax',
|
||||
'mobile', 'pager',
|
||||
'company_main', 'assistant',
|
||||
'car', 'radio', 'isdn', 'callback',
|
||||
'telex', 'tty_tdd', 'work_mobile',
|
||||
'work_pager', 'main', 'grand_central'],},},
|
||||
'posixAccounts':
|
||||
{CLASS: PC_POSIX, TITLE: 'Posix Accounts',},
|
||||
'relations':
|
||||
{CLASS: PC_ARRAY, TITLE: 'Relations',
|
||||
TYPE_KEYWORDS:
|
||||
{PTKW_CL_TYPE_KEYWORD: 'type', PTKW_CL_CUSTOM_KEYWORD: 'custom',
|
||||
PTKW_ATTR_TYPE_KEYWORD: 'type', PTKW_ATTR_TYPE_CUSTOM_VALUE: 'custom', PTKW_ATTR_CUSTOMTYPE_KEYWORD: 'customType',
|
||||
PTKW_KEYWORD_LIST: ['custom', 'spouse', 'child', 'mother',
|
||||
'father', 'parent', 'brother',
|
||||
'sister', 'friend', 'relative',
|
||||
'domestic_partner', 'partner',
|
||||
'manager', 'dotted_line_manager',
|
||||
'assistant', 'admin_assistant', 'exec_assistant',
|
||||
'referred_by'],},},
|
||||
'sshPublicKeys':
|
||||
{CLASS: PC_SSH, TITLE: 'SSH Public Keys',},
|
||||
'websites':
|
||||
{CLASS: PC_ARRAY, TITLE: 'Websites',
|
||||
TYPE_KEYWORDS:
|
||||
{PTKW_CL_TYPE_KEYWORD: 'type', PTKW_CL_CUSTOM_KEYWORD: 'custom',
|
||||
PTKW_ATTR_TYPE_KEYWORD: 'type', PTKW_ATTR_TYPE_CUSTOM_VALUE: 'custom', PTKW_ATTR_CUSTOMTYPE_KEYWORD: 'customType',
|
||||
PTKW_KEYWORD_LIST: ['custom', 'home', 'work',
|
||||
'home_page', 'ftp', 'blog',
|
||||
'profile', 'other', 'reservations',
|
||||
'app_install_page', 'resume'],},},
|
||||
'customSchemas':
|
||||
{CLASS: PC_SCHEMAS, TITLE: 'Custom Schemas',
|
||||
TYPE_KEYWORDS:
|
||||
{PTKW_CL_TYPE_KEYWORD: 'type', PTKW_CL_CUSTOM_KEYWORD: 'custom',
|
||||
PTKW_ATTR_TYPE_KEYWORD: 'type', PTKW_ATTR_TYPE_CUSTOM_VALUE: 'custom', PTKW_ATTR_CUSTOMTYPE_KEYWORD: 'customType',
|
||||
PTKW_KEYWORD_LIST: ['custom', 'home', 'other', 'work'],},},
|
||||
'aliases': {
|
||||
CLASS: PC_ALIASES, TITLE: 'Email Aliases',},
|
||||
'nonEditableAliases': {
|
||||
CLASS: PC_ALIASES, TITLE: 'NonEditable Aliases',},
|
||||
}
|
||||
#
|
||||
IM_PROTOCOLS = {
|
||||
PTKW_CL_TYPE_KEYWORD: 'protocol', PTKW_CL_CUSTOM_KEYWORD: 'custom_protocol',
|
||||
PTKW_ATTR_TYPE_KEYWORD: 'protocol', PTKW_ATTR_TYPE_CUSTOM_VALUE: 'custom_protocol', PTKW_ATTR_CUSTOMTYPE_KEYWORD: 'customProtocol',
|
||||
PTKW_KEYWORD_LIST: ['custom_protocol', 'aim', 'gtalk', 'icq', 'jabber', 'msn', 'net_meeting', 'qq', 'skype', 'xmpp', 'yahoo']
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2025 Ross Scroggs All Rights Reserved.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""GAM extended library versions
|
||||
|
||||
"""
|
||||
|
||||
GAM_VER_LIBS = [
|
||||
'arrow',
|
||||
'chardet',
|
||||
'cryptography',
|
||||
'filelock',
|
||||
'google-api-python-client',
|
||||
'google-auth-httplib2',
|
||||
'google-auth-oauthlib',
|
||||
'google-auth',
|
||||
'lxml',
|
||||
'httplib2',
|
||||
'passlib',
|
||||
'pathvalidate',
|
||||
'pyscard',
|
||||
'yubikey-manager',
|
||||
]
|
||||
@@ -1,201 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2025 Ross Scroggs All Rights Reserved.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""YubiKey"""
|
||||
|
||||
import base64
|
||||
from datetime import datetime, timedelta
|
||||
from secrets import SystemRandom
|
||||
import string
|
||||
import sys
|
||||
|
||||
from gam import mplock
|
||||
|
||||
from gam import systemErrorExit
|
||||
from gam import readStdin
|
||||
from gam import writeStdout
|
||||
|
||||
from gam.gamlib import glmsgs as Msg
|
||||
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
from smartcard.Exceptions import CardConnectionException
|
||||
from ykman.device import list_all_devices
|
||||
from ykman.piv import generate_self_signed_certificate, generate_chuid
|
||||
from yubikit.piv import DEFAULT_MANAGEMENT_KEY, \
|
||||
InvalidPinError, \
|
||||
KEY_TYPE, \
|
||||
PIN_POLICY, \
|
||||
PivSession, \
|
||||
OBJECT_ID, \
|
||||
SLOT, \
|
||||
TOUCH_POLICY
|
||||
from yubikit.core.smartcard import ApduError, SmartCardConnection
|
||||
|
||||
YUBIKEY_CONNECTION_ERROR_RC = 80
|
||||
YUBIKEY_INVALID_KEY_TYPE_RC = 81
|
||||
YUBIKEY_INVALID_SLOT_RC = 82
|
||||
YUBIKEY_INVALID_PIN_RC = 83
|
||||
YUBIKEY_APDU_ERROR_RC = 84
|
||||
YUBIKEY_VALUE_ERROR_RC = 85
|
||||
YUBIKEY_MULTIPLE_CONNECTED_RC = 86
|
||||
YUBIKEY_NOT_FOUND_RC = 87
|
||||
|
||||
PIN_PUK_CHARS = string.ascii_letters+string.digits+string.punctuation
|
||||
|
||||
class YubiKey():
|
||||
|
||||
def __init__(self, service_account_info=None):
|
||||
self.key_type = None
|
||||
self.slot = None
|
||||
self.serial_number = None
|
||||
self.pin = None
|
||||
self.key_id = None
|
||||
if service_account_info:
|
||||
key_type = service_account_info.get('yubikey_key_type', 'RSA2048')
|
||||
try:
|
||||
self.key_type = getattr(KEY_TYPE, key_type.upper())
|
||||
except AttributeError:
|
||||
systemErrorExit(YUBIKEY_INVALID_KEY_TYPE_RC, f'{key_type} is not a valid value for yubikey_key_type')
|
||||
slot = service_account_info.get('yubikey_slot', 'AUTHENTICATION')
|
||||
try:
|
||||
self.slot = getattr(SLOT, slot.upper())
|
||||
except AttributeError:
|
||||
systemErrorExit(YUBIKEY_INVALID_SLOT_RC, f'{slot} is not a valid value for yubikey_slot')
|
||||
self.serial_number = service_account_info.get('yubikey_serial_number')
|
||||
self.pin = service_account_info.get('yubikey_pin')
|
||||
self.key_id = service_account_info.get('private_key_id')
|
||||
|
||||
def _connect(self):
|
||||
try:
|
||||
devices = list_all_devices()
|
||||
for (device, info) in devices:
|
||||
if info.serial == self.serial_number:
|
||||
return device.open_connection(SmartCardConnection)
|
||||
except CardConnectionException as err:
|
||||
systemErrorExit(YUBIKEY_CONNECTION_ERROR_RC, f'YubiKey - {err}')
|
||||
|
||||
def get_certificate(self):
|
||||
try:
|
||||
conn = self._connect()
|
||||
with conn:
|
||||
session = PivSession(conn)
|
||||
if self.pin:
|
||||
try:
|
||||
session.verify_pin(self.pin)
|
||||
except InvalidPinError as err:
|
||||
systemErrorExit(YUBIKEY_INVALID_PIN_RC, f'YubiKey - {err}')
|
||||
try:
|
||||
cert = session.get_certificate(self.slot)
|
||||
except ApduError as err:
|
||||
systemErrorExit(YUBIKEY_APDU_ERROR_RC, f'YubiKey - {err}')
|
||||
cert_pem = cert.public_bytes(serialization.Encoding.PEM).decode()
|
||||
publicKeyData = base64.b64encode(cert_pem.encode())
|
||||
if isinstance(publicKeyData, bytes):
|
||||
publicKeyData = publicKeyData.decode()
|
||||
return publicKeyData
|
||||
except ValueError as err:
|
||||
systemErrorExit(YUBIKEY_VALUE_ERROR_RC, f'YubiKey - {err}')
|
||||
except TypeError as err:
|
||||
systemErrorExit(YUBIKEY_NOT_FOUND_RC, f'YubiKey - {err} - {Msg.IS_YUBIKEY_INSERTED}')
|
||||
|
||||
def get_serial_number(self):
|
||||
try:
|
||||
devices = list_all_devices()
|
||||
if not devices:
|
||||
systemErrorExit(YUBIKEY_NOT_FOUND_RC, Msg.COULD_NOT_FIND_ANY_YUBIKEY)
|
||||
if self.serial_number:
|
||||
for (_, info) in devices:
|
||||
if info.serial == self.serial_number:
|
||||
return info.serial
|
||||
systemErrorExit(YUBIKEY_NOT_FOUND_RC, Msg.COULD_NOT_FIND_YUBIKEY_WITH_SERIAL.format(self.serial_number))
|
||||
if len(devices) > 1:
|
||||
serials = ', '.join([str(info.serial) for (_, info) in devices])
|
||||
systemErrorExit(YUBIKEY_MULTIPLE_CONNECTED_RC, Msg.MULTIPLE_YUBIKEYS_CONNECTED.format(serials))
|
||||
return devices[0][1].serial
|
||||
except ValueError as err:
|
||||
systemErrorExit(YUBIKEY_VALUE_ERROR_RC, f'YubiKey - {err}')
|
||||
|
||||
def reset_piv(self):
|
||||
'''Resets YubiKey PIV app and generates new key for GAM to use.'''
|
||||
reply = str(readStdin(Msg.CONFIRM_WIPE_YUBIKEY_PIV).lower().strip())
|
||||
if reply != 'y':
|
||||
sys.exit(1)
|
||||
try:
|
||||
conn = self._connect()
|
||||
with conn:
|
||||
piv = PivSession(conn)
|
||||
piv.reset()
|
||||
rnd = SystemRandom()
|
||||
new_puk = ''.join(rnd.choice(PIN_PUK_CHARS) for _ in range(8))
|
||||
new_pin = ''.join(rnd.choice(PIN_PUK_CHARS) for _ in range(8))
|
||||
piv.change_puk('12345678', new_puk)
|
||||
piv.change_pin('123456', new_pin)
|
||||
writeStdout(Msg.YUBIKEY_PIN_SET_TO.format(new_pin))
|
||||
piv.authenticate(piv.management_key_type, DEFAULT_MANAGEMENT_KEY)
|
||||
piv.verify_pin(new_pin)
|
||||
writeStdout(Msg.YUBIKEY_GENERATING_NONEXPORTABLE_PRIVATE_KEY)
|
||||
pubkey = piv.generate_key(SLOT.AUTHENTICATION,
|
||||
KEY_TYPE.RSA2048,
|
||||
PIN_POLICY.ALWAYS,
|
||||
TOUCH_POLICY.NEVER)
|
||||
now = datetime.utcnow()
|
||||
valid_to = now + timedelta(days=3650)
|
||||
subject = 'CN=GAM Created Key'
|
||||
piv.authenticate(piv.management_key_type, DEFAULT_MANAGEMENT_KEY)
|
||||
piv.verify_pin(new_pin)
|
||||
cert = generate_self_signed_certificate(piv,
|
||||
SLOT.AUTHENTICATION,
|
||||
pubkey,
|
||||
subject,
|
||||
now,
|
||||
valid_to)
|
||||
piv.put_certificate(SLOT.AUTHENTICATION, cert)
|
||||
piv.put_object(OBJECT_ID.CHUID, generate_chuid())
|
||||
except ValueError as err:
|
||||
systemErrorExit(YUBIKEY_VALUE_ERROR_RC, f'YubiKey - {err}')
|
||||
except TypeError as err:
|
||||
systemErrorExit(YUBIKEY_NOT_FOUND_RC, f'YubiKey - {err} - {Msg.IS_YUBIKEY_INSERTED}')
|
||||
|
||||
def sign(self, message):
|
||||
if mplock is not None:
|
||||
mplock.acquire()
|
||||
try:
|
||||
conn = self._connect()
|
||||
with conn:
|
||||
session = PivSession(conn)
|
||||
if self.pin:
|
||||
try:
|
||||
session.verify_pin(self.pin)
|
||||
except InvalidPinError as err:
|
||||
systemErrorExit(YUBIKEY_INVALID_PIN_RC, f'YubiKey - {err}')
|
||||
try:
|
||||
signed = session.sign(slot=self.slot,
|
||||
key_type=self.key_type,
|
||||
message=message,
|
||||
hash_algorithm=hashes.SHA256(),
|
||||
padding=padding.PKCS1v15())
|
||||
except ApduError as err:
|
||||
systemErrorExit(YUBIKEY_APDU_ERROR_RC, f'YubiKey - {err}')
|
||||
except ValueError as err:
|
||||
systemErrorExit(YUBIKEY_VALUE_ERROR_RC, f'YubiKey - {err}')
|
||||
except TypeError as err:
|
||||
systemErrorExit(YUBIKEY_NOT_FOUND_RC, f'YubiKey - {err} - {Msg.IS_YUBIKEY_INSERTED}')
|
||||
if mplock is not None:
|
||||
mplock.release()
|
||||
return signed
|
||||
@@ -1,825 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright (C) 2006 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
"""Contains classes representing Google Data elements.
|
||||
|
||||
Extends Atom classes to add Google Data specific elements.
|
||||
"""
|
||||
|
||||
|
||||
__author__ = 'j.s@google.com (Jeffrey Scudder)'
|
||||
|
||||
import os
|
||||
import atom
|
||||
import lxml.etree as ElementTree
|
||||
|
||||
# XML namespaces which are often used in GData entities.
|
||||
GDATA_NAMESPACE = 'http://schemas.google.com/g/2005'
|
||||
GDATA_TEMPLATE = '{http://schemas.google.com/g/2005}%s'
|
||||
OPENSEARCH_NAMESPACE = 'http://a9.com/-/spec/opensearchrss/1.0/'
|
||||
OPENSEARCH_TEMPLATE = '{http://a9.com/-/spec/opensearchrss/1.0/}%s'
|
||||
BATCH_NAMESPACE = 'http://schemas.google.com/gdata/batch'
|
||||
GACL_NAMESPACE = 'http://schemas.google.com/acl/2007'
|
||||
GACL_TEMPLATE = '{http://schemas.google.com/acl/2007}%s'
|
||||
|
||||
|
||||
# Labels used in batch request entries to specify the desired CRUD operation.
|
||||
BATCH_INSERT = 'insert'
|
||||
BATCH_UPDATE = 'update'
|
||||
BATCH_DELETE = 'delete'
|
||||
BATCH_QUERY = 'query'
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MissingRequiredParameters(Error):
|
||||
pass
|
||||
|
||||
|
||||
class MediaSource(object):
|
||||
"""GData Entries can refer to media sources, so this class provides a
|
||||
place to store references to these objects along with some metadata.
|
||||
"""
|
||||
|
||||
def __init__(self, file_handle=None, content_type=None, content_length=None,
|
||||
file_path=None, file_name=None):
|
||||
"""Creates an object of type MediaSource.
|
||||
|
||||
Args:
|
||||
file_handle: A file handle pointing to the file to be encapsulated in the
|
||||
MediaSource
|
||||
content_type: string The MIME type of the file. Required if a file_handle
|
||||
is given.
|
||||
content_length: int The size of the file. Required if a file_handle is
|
||||
given.
|
||||
file_path: string (optional) A full path name to the file. Used in
|
||||
place of a file_handle.
|
||||
file_name: string The name of the file without any path information.
|
||||
Required if a file_handle is given.
|
||||
"""
|
||||
self.file_handle = file_handle
|
||||
self.content_type = content_type
|
||||
self.content_length = content_length
|
||||
self.file_name = file_name
|
||||
|
||||
if (file_handle is None and content_type is not None and
|
||||
file_path is not None):
|
||||
self.setFile(file_path, content_type)
|
||||
|
||||
def setFile(self, file_name, content_type):
|
||||
"""A helper function which can create a file handle from a given filename
|
||||
and set the content type and length all at once.
|
||||
|
||||
Args:
|
||||
file_name: string The path and file name to the file containing the media
|
||||
content_type: string A MIME type representing the type of the media
|
||||
"""
|
||||
|
||||
self.file_handle = open(file_name, 'rb')
|
||||
self.content_type = content_type
|
||||
self.content_length = os.path.getsize(file_name)
|
||||
self.file_name = os.path.basename(file_name)
|
||||
|
||||
|
||||
class LinkFinder(atom.LinkFinder):
|
||||
"""An "interface" providing methods to find link elements
|
||||
|
||||
GData Entry elements often contain multiple links which differ in the rel
|
||||
attribute or content type. Often, developers are interested in a specific
|
||||
type of link so this class provides methods to find specific classes of
|
||||
links.
|
||||
|
||||
This class is used as a mixin in GData entries.
|
||||
"""
|
||||
|
||||
def GetSelfLink(self):
|
||||
"""Find the first link with rel set to 'self'
|
||||
|
||||
Returns:
|
||||
An atom.Link or none if none of the links had rel equal to 'self'
|
||||
"""
|
||||
|
||||
for a_link in self.link:
|
||||
if a_link.rel == 'self':
|
||||
return a_link
|
||||
return None
|
||||
|
||||
def GetEditLink(self):
|
||||
for a_link in self.link:
|
||||
if a_link.rel == 'edit':
|
||||
return a_link
|
||||
return None
|
||||
|
||||
def GetEditMediaLink(self):
|
||||
"""The Picasa API mistakenly returns media-edit rather than edit-media, but
|
||||
this may change soon.
|
||||
"""
|
||||
for a_link in self.link:
|
||||
if a_link.rel == 'edit-media':
|
||||
return a_link
|
||||
if a_link.rel == 'media-edit':
|
||||
return a_link
|
||||
return None
|
||||
|
||||
def GetHtmlLink(self):
|
||||
"""Find the first link with rel of alternate and type of text/html
|
||||
|
||||
Returns:
|
||||
An atom.Link or None if no links matched
|
||||
"""
|
||||
for a_link in self.link:
|
||||
if a_link.rel == 'alternate' and a_link.type == 'text/html':
|
||||
return a_link
|
||||
return None
|
||||
|
||||
def GetPostLink(self):
|
||||
"""Get a link containing the POST target URL.
|
||||
|
||||
The POST target URL is used to insert new entries.
|
||||
|
||||
Returns:
|
||||
A link object with a rel matching the POST type.
|
||||
"""
|
||||
for a_link in self.link:
|
||||
if a_link.rel == 'http://schemas.google.com/g/2005#post':
|
||||
return a_link
|
||||
return None
|
||||
|
||||
def GetAclLink(self):
|
||||
for a_link in self.link:
|
||||
if a_link.rel == 'http://schemas.google.com/acl/2007#accessControlList':
|
||||
return a_link
|
||||
return None
|
||||
|
||||
def GetFeedLink(self):
|
||||
for a_link in self.link:
|
||||
if a_link.rel == 'http://schemas.google.com/g/2005#feed':
|
||||
return a_link
|
||||
return None
|
||||
|
||||
def GetNextLink(self):
|
||||
for a_link in self.link:
|
||||
if a_link.rel == 'next':
|
||||
return a_link
|
||||
return None
|
||||
|
||||
def GetPrevLink(self):
|
||||
for a_link in self.link:
|
||||
if a_link.rel == 'previous':
|
||||
return a_link
|
||||
return None
|
||||
|
||||
|
||||
class TotalResults(atom.AtomBase):
|
||||
"""opensearch:TotalResults for a GData feed"""
|
||||
|
||||
_tag = 'totalResults'
|
||||
_namespace = OPENSEARCH_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
|
||||
def __init__(self, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def TotalResultsFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(TotalResults, xml_string)
|
||||
|
||||
|
||||
class StartIndex(atom.AtomBase):
|
||||
"""The opensearch:startIndex element in GData feed"""
|
||||
|
||||
_tag = 'startIndex'
|
||||
_namespace = OPENSEARCH_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
|
||||
def __init__(self, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def StartIndexFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(StartIndex, xml_string)
|
||||
|
||||
|
||||
class ItemsPerPage(atom.AtomBase):
|
||||
"""The opensearch:itemsPerPage element in GData feed"""
|
||||
|
||||
_tag = 'itemsPerPage'
|
||||
_namespace = OPENSEARCH_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
|
||||
def __init__(self, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def ItemsPerPageFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(ItemsPerPage, xml_string)
|
||||
|
||||
|
||||
class ExtendedProperty(atom.AtomBase):
|
||||
"""The Google Data extendedProperty element.
|
||||
|
||||
Used to store arbitrary key-value information specific to your
|
||||
application. The value can either be a text string stored as an XML
|
||||
attribute (.value), or an XML node (XmlBlob) as a child element.
|
||||
|
||||
This element is used in the Google Calendar data API and the Google
|
||||
Contacts data API.
|
||||
"""
|
||||
|
||||
_tag = 'extendedProperty'
|
||||
_namespace = GDATA_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['name'] = 'name'
|
||||
_attributes['value'] = 'value'
|
||||
|
||||
def __init__(self, name=None, value=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
self.name = name
|
||||
self.value = value
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
def GetXmlBlobExtensionElement(self):
|
||||
"""Returns the XML blob as an atom.ExtensionElement.
|
||||
|
||||
Returns:
|
||||
An atom.ExtensionElement representing the blob's XML, or None if no
|
||||
blob was set.
|
||||
"""
|
||||
if len(self.extension_elements) < 1:
|
||||
return None
|
||||
else:
|
||||
return self.extension_elements[0]
|
||||
|
||||
def GetXmlBlobString(self):
|
||||
"""Returns the XML blob as a string.
|
||||
|
||||
Returns:
|
||||
A string containing the blob's XML, or None if no blob was set.
|
||||
"""
|
||||
blob = self.GetXmlBlobExtensionElement()
|
||||
if blob:
|
||||
return blob.ToString()
|
||||
return None
|
||||
|
||||
def SetXmlBlob(self, blob):
|
||||
"""Sets the contents of the extendedProperty to XML as a child node.
|
||||
|
||||
Since the extendedProperty is only allowed one child element as an XML
|
||||
blob, setting the XML blob will erase any preexisting extension elements
|
||||
in this object.
|
||||
|
||||
Args:
|
||||
blob: str, ElementTree Element or atom.ExtensionElement representing
|
||||
the XML blob stored in the extendedProperty.
|
||||
"""
|
||||
# Erase any existing extension_elements, clears the child nodes from the
|
||||
# extendedProperty.
|
||||
self.extension_elements = []
|
||||
if isinstance(blob, atom.ExtensionElement):
|
||||
self.extension_elements.append(blob)
|
||||
elif ElementTree.iselement(blob):
|
||||
self.extension_elements.append(atom._ExtensionElementFromElementTree(
|
||||
blob))
|
||||
else:
|
||||
self.extension_elements.append(atom.ExtensionElementFromString(blob))
|
||||
|
||||
|
||||
def ExtendedPropertyFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(ExtendedProperty, xml_string)
|
||||
|
||||
|
||||
class GDataEntry(atom.Entry, LinkFinder):
|
||||
"""Extends Atom Entry to provide data processing"""
|
||||
|
||||
_tag = atom.Entry._tag
|
||||
_namespace = atom.Entry._namespace
|
||||
_children = atom.Entry._children.copy()
|
||||
_attributes = atom.Entry._attributes.copy()
|
||||
|
||||
def __GetId(self):
|
||||
return self.__id
|
||||
|
||||
# This method was created to strip the unwanted whitespace from the id's
|
||||
# text node.
|
||||
def __SetId(self, id):
|
||||
self.__id = id
|
||||
if id is not None and id.text is not None:
|
||||
self.__id.text = id.text.strip()
|
||||
|
||||
id = property(__GetId, __SetId)
|
||||
|
||||
def IsMedia(self):
|
||||
"""Determines whether or not an entry is a GData Media entry.
|
||||
"""
|
||||
if (self.GetEditMediaLink()):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def GetMediaURL(self):
|
||||
"""Returns the URL to the media content, if the entry is a media entry.
|
||||
Otherwise returns None.
|
||||
"""
|
||||
if not self.IsMedia():
|
||||
return None
|
||||
else:
|
||||
return self.content.src
|
||||
|
||||
|
||||
def GDataEntryFromString(xml_string):
|
||||
"""Creates a new GDataEntry instance given a string of XML."""
|
||||
return atom.CreateClassFromXMLString(GDataEntry, xml_string)
|
||||
|
||||
|
||||
class GDataFeed(atom.Feed, LinkFinder):
|
||||
"""A Feed from a GData service"""
|
||||
|
||||
_tag = 'feed'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = atom.Feed._children.copy()
|
||||
_attributes = atom.Feed._attributes.copy()
|
||||
_children['{%s}totalResults' % OPENSEARCH_NAMESPACE] = ('total_results',
|
||||
TotalResults)
|
||||
_children['{%s}startIndex' % OPENSEARCH_NAMESPACE] = ('start_index',
|
||||
StartIndex)
|
||||
_children['{%s}itemsPerPage' % OPENSEARCH_NAMESPACE] = ('items_per_page',
|
||||
ItemsPerPage)
|
||||
# Add a conversion rule for atom:entry to make it into a GData
|
||||
# Entry.
|
||||
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [GDataEntry])
|
||||
|
||||
def __GetId(self):
|
||||
return self.__id
|
||||
|
||||
def __SetId(self, id):
|
||||
self.__id = id
|
||||
if id is not None and id.text is not None:
|
||||
self.__id.text = id.text.strip()
|
||||
|
||||
id = property(__GetId, __SetId)
|
||||
|
||||
def __GetGenerator(self):
|
||||
return self.__generator
|
||||
|
||||
def __SetGenerator(self, generator):
|
||||
self.__generator = generator
|
||||
if generator is not None:
|
||||
self.__generator.text = generator.text.strip()
|
||||
|
||||
generator = property(__GetGenerator, __SetGenerator)
|
||||
|
||||
def __init__(self, author=None, category=None, contributor=None,
|
||||
generator=None, icon=None, atom_id=None, link=None, logo=None,
|
||||
rights=None, subtitle=None, title=None, updated=None, entry=None,
|
||||
total_results=None, start_index=None, items_per_page=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
"""Constructor for Source
|
||||
|
||||
Args:
|
||||
author: list (optional) A list of Author instances which belong to this
|
||||
class.
|
||||
category: list (optional) A list of Category instances
|
||||
contributor: list (optional) A list on Contributor instances
|
||||
generator: Generator (optional)
|
||||
icon: Icon (optional)
|
||||
id: Id (optional) The entry's Id element
|
||||
link: list (optional) A list of Link instances
|
||||
logo: Logo (optional)
|
||||
rights: Rights (optional) The entry's Rights element
|
||||
subtitle: Subtitle (optional) The entry's subtitle element
|
||||
title: Title (optional) the entry's title element
|
||||
updated: Updated (optional) the entry's updated element
|
||||
entry: list (optional) A list of the Entry instances contained in the
|
||||
feed.
|
||||
text: String (optional) The text contents of the element. This is the
|
||||
contents of the Entry's XML text node.
|
||||
(Example: <foo>This is the text</foo>)
|
||||
extension_elements: list (optional) A list of ExtensionElement instances
|
||||
which are children of this element.
|
||||
extension_attributes: dict (optional) A dictionary of strings which are
|
||||
the values for additional XML attributes of this element.
|
||||
"""
|
||||
|
||||
self.author = author or []
|
||||
self.category = category or []
|
||||
self.contributor = contributor or []
|
||||
self.generator = generator
|
||||
self.icon = icon
|
||||
self.id = atom_id
|
||||
self.link = link or []
|
||||
self.logo = logo
|
||||
self.rights = rights
|
||||
self.subtitle = subtitle
|
||||
self.title = title
|
||||
self.updated = updated
|
||||
self.entry = entry or []
|
||||
self.total_results = total_results
|
||||
self.start_index = start_index
|
||||
self.items_per_page = items_per_page
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def GDataFeedFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(GDataFeed, xml_string)
|
||||
|
||||
|
||||
class BatchId(atom.AtomBase):
|
||||
_tag = 'id'
|
||||
_namespace = BATCH_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
|
||||
|
||||
def BatchIdFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(BatchId, xml_string)
|
||||
|
||||
|
||||
class BatchOperation(atom.AtomBase):
|
||||
_tag = 'operation'
|
||||
_namespace = BATCH_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['type'] = 'type'
|
||||
|
||||
def __init__(self, op_type=None, extension_elements=None,
|
||||
extension_attributes=None,
|
||||
text=None):
|
||||
self.type = op_type
|
||||
atom.AtomBase.__init__(self,
|
||||
extension_elements=extension_elements,
|
||||
extension_attributes=extension_attributes,
|
||||
text=text)
|
||||
|
||||
|
||||
def BatchOperationFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(BatchOperation, xml_string)
|
||||
|
||||
|
||||
class BatchStatus(atom.AtomBase):
|
||||
"""The batch:status element present in a batch response entry.
|
||||
|
||||
A status element contains the code (HTTP response code) and
|
||||
reason as elements. In a single request these fields would
|
||||
be part of the HTTP response, but in a batch request each
|
||||
Entry operation has a corresponding Entry in the response
|
||||
feed which includes status information.
|
||||
|
||||
See http://code.google.com/apis/gdata/batch.html#Handling_Errors
|
||||
"""
|
||||
|
||||
_tag = 'status'
|
||||
_namespace = BATCH_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['code'] = 'code'
|
||||
_attributes['reason'] = 'reason'
|
||||
_attributes['content-type'] = 'content_type'
|
||||
|
||||
def __init__(self, code=None, reason=None, content_type=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
self.code = code
|
||||
self.reason = reason
|
||||
self.content_type = content_type
|
||||
atom.AtomBase.__init__(self, extension_elements=extension_elements,
|
||||
extension_attributes=extension_attributes,
|
||||
text=text)
|
||||
|
||||
|
||||
def BatchStatusFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(BatchStatus, xml_string)
|
||||
|
||||
|
||||
class BatchEntry(GDataEntry):
|
||||
"""An atom:entry for use in batch requests.
|
||||
|
||||
The BatchEntry contains additional members to specify the operation to be
|
||||
performed on this entry and a batch ID so that the server can reference
|
||||
individual operations in the response feed. For more information, see:
|
||||
http://code.google.com/apis/gdata/batch.html
|
||||
"""
|
||||
|
||||
_tag = GDataEntry._tag
|
||||
_namespace = GDataEntry._namespace
|
||||
_children = GDataEntry._children.copy()
|
||||
_children['{%s}operation' % BATCH_NAMESPACE] = ('batch_operation', BatchOperation)
|
||||
_children['{%s}id' % BATCH_NAMESPACE] = ('batch_id', BatchId)
|
||||
_children['{%s}status' % BATCH_NAMESPACE] = ('batch_status', BatchStatus)
|
||||
_attributes = GDataEntry._attributes.copy()
|
||||
|
||||
def __init__(self, author=None, category=None, content=None,
|
||||
contributor=None, atom_id=None, link=None, published=None, rights=None,
|
||||
source=None, summary=None, control=None, title=None, updated=None,
|
||||
batch_operation=None, batch_id=None, batch_status=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
self.batch_operation = batch_operation
|
||||
self.batch_id = batch_id
|
||||
self.batch_status = batch_status
|
||||
GDataEntry.__init__(self, author=author, category=category,
|
||||
content=content, contributor=contributor, atom_id=atom_id, link=link,
|
||||
published=published, rights=rights, source=source, summary=summary,
|
||||
control=control, title=title, updated=updated,
|
||||
extension_elements=extension_elements,
|
||||
extension_attributes=extension_attributes, text=text)
|
||||
|
||||
|
||||
def BatchEntryFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(BatchEntry, xml_string)
|
||||
|
||||
|
||||
class BatchInterrupted(atom.AtomBase):
|
||||
"""The batch:interrupted element sent if batch request was interrupted.
|
||||
|
||||
Only appears in a feed if some of the batch entries could not be processed.
|
||||
See: http://code.google.com/apis/gdata/batch.html#Handling_Errors
|
||||
"""
|
||||
|
||||
_tag = 'interrupted'
|
||||
_namespace = BATCH_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['reason'] = 'reason'
|
||||
_attributes['success'] = 'success'
|
||||
_attributes['failures'] = 'failures'
|
||||
_attributes['parsed'] = 'parsed'
|
||||
|
||||
def __init__(self, reason=None, success=None, failures=None, parsed=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
self.reason = reason
|
||||
self.success = success
|
||||
self.failures = failures
|
||||
self.parsed = parsed
|
||||
atom.AtomBase.__init__(self, extension_elements=extension_elements,
|
||||
extension_attributes=extension_attributes,
|
||||
text=text)
|
||||
|
||||
|
||||
def BatchInterruptedFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(BatchInterrupted, xml_string)
|
||||
|
||||
|
||||
class BatchFeed(GDataFeed):
|
||||
"""A feed containing a list of batch request entries."""
|
||||
|
||||
_tag = GDataFeed._tag
|
||||
_namespace = GDataFeed._namespace
|
||||
_children = GDataFeed._children.copy()
|
||||
_attributes = GDataFeed._attributes.copy()
|
||||
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [BatchEntry])
|
||||
_children['{%s}interrupted' % BATCH_NAMESPACE] = ('interrupted', BatchInterrupted)
|
||||
|
||||
def __init__(self, author=None, category=None, contributor=None,
|
||||
generator=None, icon=None, atom_id=None, link=None, logo=None,
|
||||
rights=None, subtitle=None, title=None, updated=None, entry=None,
|
||||
total_results=None, start_index=None, items_per_page=None,
|
||||
interrupted=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
self.interrupted = interrupted
|
||||
GDataFeed.__init__(self, author=author, category=category,
|
||||
contributor=contributor, generator=generator,
|
||||
icon=icon, atom_id=atom_id, link=link,
|
||||
logo=logo, rights=rights, subtitle=subtitle,
|
||||
title=title, updated=updated, entry=entry,
|
||||
total_results=total_results, start_index=start_index,
|
||||
items_per_page=items_per_page,
|
||||
extension_elements=extension_elements,
|
||||
extension_attributes=extension_attributes,
|
||||
text=text)
|
||||
|
||||
def AddBatchEntry(self, entry=None, id_url_string=None,
|
||||
batch_id_string=None, operation_string=None):
|
||||
"""Logic for populating members of a BatchEntry and adding to the feed.
|
||||
|
||||
|
||||
If the entry is not a BatchEntry, it is converted to a BatchEntry so
|
||||
that the batch specific members will be present.
|
||||
|
||||
The id_url_string can be used in place of an entry if the batch operation
|
||||
applies to a URL. For example query and delete operations require just
|
||||
the URL of an entry, no body is sent in the HTTP request. If an
|
||||
id_url_string is sent instead of an entry, a BatchEntry is created and
|
||||
added to the feed.
|
||||
|
||||
This method also assigns the desired batch id to the entry so that it
|
||||
can be referenced in the server's response. If the batch_id_string is
|
||||
None, this method will assign a batch_id to be the index at which this
|
||||
entry will be in the feed's entry list.
|
||||
|
||||
Args:
|
||||
entry: BatchEntry, atom.Entry, or another Entry flavor (optional) The
|
||||
entry which will be sent to the server as part of the batch request.
|
||||
The item must have a valid atom id so that the server knows which
|
||||
entry this request references.
|
||||
id_url_string: str (optional) The URL of the entry to be acted on. You
|
||||
can find this URL in the text member of the atom id for an entry.
|
||||
If an entry is not sent, this id will be used to construct a new
|
||||
BatchEntry which will be added to the request feed.
|
||||
batch_id_string: str (optional) The batch ID to be used to reference
|
||||
this batch operation in the results feed. If this parameter is None,
|
||||
the current length of the feed's entry array will be used as a
|
||||
count. Note that batch_ids should either always be specified or
|
||||
never, mixing could potentially result in duplicate batch ids.
|
||||
operation_string: str (optional) The desired batch operation which will
|
||||
set the batch_operation.type member of the entry. Options are
|
||||
'insert', 'update', 'delete', and 'query'
|
||||
|
||||
Raises:
|
||||
MissingRequiredParameters: Raised if neither an id_ url_string nor an
|
||||
entry are provided in the request.
|
||||
|
||||
Returns:
|
||||
The added entry.
|
||||
"""
|
||||
if entry is None and id_url_string is None:
|
||||
raise MissingRequiredParameters('supply either an entry or URL string')
|
||||
if entry is None and id_url_string is not None:
|
||||
entry = BatchEntry(atom_id=atom.Id(text=id_url_string))
|
||||
# TODO: handle cases in which the entry lacks batch_... members.
|
||||
#if not isinstance(entry, BatchEntry):
|
||||
# Convert the entry to a batch entry.
|
||||
if batch_id_string is not None:
|
||||
entry.batch_id = BatchId(text=batch_id_string)
|
||||
elif entry.batch_id is None or entry.batch_id.text is None:
|
||||
entry.batch_id = BatchId(text=str(len(self.entry)))
|
||||
if operation_string is not None:
|
||||
entry.batch_operation = BatchOperation(op_type=operation_string)
|
||||
self.entry.append(entry)
|
||||
return entry
|
||||
|
||||
def AddInsert(self, entry, batch_id_string=None):
|
||||
"""Add an insert request to the operations in this batch request feed.
|
||||
|
||||
If the entry doesn't yet have an operation or a batch id, these will
|
||||
be set to the insert operation and a batch_id specified as a parameter.
|
||||
|
||||
Args:
|
||||
entry: BatchEntry The entry which will be sent in the batch feed as an
|
||||
insert request.
|
||||
batch_id_string: str (optional) The batch ID to be used to reference
|
||||
this batch operation in the results feed. If this parameter is None,
|
||||
the current length of the feed's entry array will be used as a
|
||||
count. Note that batch_ids should either always be specified or
|
||||
never, mixing could potentially result in duplicate batch ids.
|
||||
"""
|
||||
entry = self.AddBatchEntry(entry=entry, batch_id_string=batch_id_string,
|
||||
operation_string=BATCH_INSERT)
|
||||
|
||||
def AddUpdate(self, entry, batch_id_string=None):
|
||||
"""Add an update request to the list of batch operations in this feed.
|
||||
|
||||
Sets the operation type of the entry to insert if it is not already set
|
||||
and assigns the desired batch id to the entry so that it can be
|
||||
referenced in the server's response.
|
||||
|
||||
Args:
|
||||
entry: BatchEntry The entry which will be sent to the server as an
|
||||
update (HTTP PUT) request. The item must have a valid atom id
|
||||
so that the server knows which entry to replace.
|
||||
batch_id_string: str (optional) The batch ID to be used to reference
|
||||
this batch operation in the results feed. If this parameter is None,
|
||||
the current length of the feed's entry array will be used as a
|
||||
count. See also comments for AddInsert.
|
||||
"""
|
||||
entry = self.AddBatchEntry(entry=entry, batch_id_string=batch_id_string,
|
||||
operation_string=BATCH_UPDATE)
|
||||
|
||||
def AddDelete(self, url_string=None, entry=None, batch_id_string=None):
|
||||
"""Adds a delete request to the batch request feed.
|
||||
|
||||
This method takes either the url_string which is the atom id of the item
|
||||
to be deleted, or the entry itself. The atom id of the entry must be
|
||||
present so that the server knows which entry should be deleted.
|
||||
|
||||
Args:
|
||||
url_string: str (optional) The URL of the entry to be deleted. You can
|
||||
find this URL in the text member of the atom id for an entry.
|
||||
entry: BatchEntry (optional) The entry to be deleted.
|
||||
batch_id_string: str (optional)
|
||||
|
||||
Raises:
|
||||
MissingRequiredParameters: Raised if neither a url_string nor an entry
|
||||
are provided in the request.
|
||||
"""
|
||||
entry = self.AddBatchEntry(entry=entry, id_url_string=url_string,
|
||||
batch_id_string=batch_id_string,
|
||||
operation_string=BATCH_DELETE)
|
||||
|
||||
def AddQuery(self, url_string=None, entry=None, batch_id_string=None):
|
||||
"""Adds a query request to the batch request feed.
|
||||
|
||||
This method takes either the url_string which is the query URL
|
||||
whose results will be added to the result feed. The query URL will
|
||||
be encapsulated in a BatchEntry, and you may pass in the BatchEntry
|
||||
with a query URL instead of sending a url_string.
|
||||
|
||||
Args:
|
||||
url_string: str (optional)
|
||||
entry: BatchEntry (optional)
|
||||
batch_id_string: str (optional)
|
||||
|
||||
Raises:
|
||||
MissingRequiredParameters
|
||||
"""
|
||||
entry = self.AddBatchEntry(entry=entry, id_url_string=url_string,
|
||||
batch_id_string=batch_id_string,
|
||||
operation_string=BATCH_QUERY)
|
||||
|
||||
def GetBatchLink(self):
|
||||
for link in self.link:
|
||||
if link.rel == 'http://schemas.google.com/g/2005#batch':
|
||||
return link
|
||||
return None
|
||||
|
||||
|
||||
def BatchFeedFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(BatchFeed, xml_string)
|
||||
|
||||
|
||||
class EntryLink(atom.AtomBase):
|
||||
"""The gd:entryLink element"""
|
||||
|
||||
_tag = 'entryLink'
|
||||
_namespace = GDATA_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
# The entry used to be an atom.Entry, now it is a GDataEntry.
|
||||
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', GDataEntry)
|
||||
_attributes['rel'] = 'rel'
|
||||
_attributes['readOnly'] = 'read_only'
|
||||
_attributes['href'] = 'href'
|
||||
|
||||
def __init__(self, href=None, read_only=None, rel=None,
|
||||
entry=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
self.href = href
|
||||
self.read_only = read_only
|
||||
self.rel = rel
|
||||
self.entry = entry
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def EntryLinkFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(EntryLink, xml_string)
|
||||
|
||||
|
||||
class FeedLink(atom.AtomBase):
|
||||
"""The gd:feedLink element"""
|
||||
|
||||
_tag = 'feedLink'
|
||||
_namespace = GDATA_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_children['{%s}feed' % atom.ATOM_NAMESPACE] = ('feed', GDataFeed)
|
||||
_attributes['rel'] = 'rel'
|
||||
_attributes['readOnly'] = 'read_only'
|
||||
_attributes['countHint'] = 'count_hint'
|
||||
_attributes['href'] = 'href'
|
||||
|
||||
def __init__(self, count_hint=None, href=None, read_only=None, rel=None,
|
||||
feed=None, extension_elements=None, extension_attributes=None,
|
||||
text=None):
|
||||
self.count_hint = count_hint
|
||||
self.href = href
|
||||
self.read_only = read_only
|
||||
self.rel = rel
|
||||
self.feed = feed
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def FeedLinkFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(FeedLink, xml_string)
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright (C) 2008 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""This package's modules adapt the gdata library to run in other environments
|
||||
|
||||
The first example is the appengine module which contains functions and
|
||||
classes which modify a GDataService object to run on Google App Engine.
|
||||
"""
|
||||
@@ -1,101 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright (C) 2009 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
"""Provides functions to persist serialized auth tokens in the datastore.
|
||||
|
||||
The get_token and set_token functions should be used in conjunction with
|
||||
gdata.gauth's token_from_blob and token_to_blob to allow auth token objects
|
||||
to be reused across requests. It is up to your own code to ensure that the
|
||||
token key's are unique.
|
||||
"""
|
||||
|
||||
__author__ = 'j.s@google.com (Jeff Scudder)'
|
||||
|
||||
|
||||
from google.appengine.ext import db
|
||||
from google.appengine.api import memcache
|
||||
|
||||
|
||||
class Token(db.Model):
|
||||
"""Datastore Model which stores a serialized auth token."""
|
||||
t = db.BlobProperty()
|
||||
|
||||
|
||||
def get_token(unique_key):
|
||||
"""Searches for a stored token with the desired key.
|
||||
|
||||
Checks memcache and then the datastore if required.
|
||||
|
||||
Args:
|
||||
unique_key: str which uniquely identifies the desired auth token.
|
||||
|
||||
Returns:
|
||||
A string encoding the auth token data. Use gdata.gauth.token_from_blob to
|
||||
convert back into a usable token object. None if the token was not found
|
||||
in memcache or the datastore.
|
||||
"""
|
||||
token_string = memcache.get(unique_key)
|
||||
if token_string is None:
|
||||
# The token wasn't in memcache, so look in the datastore.
|
||||
token = Token.get_by_key_name(unique_key)
|
||||
if token is None:
|
||||
return None
|
||||
return token.t
|
||||
return token_string
|
||||
|
||||
|
||||
def set_token(unique_key, token_str):
|
||||
"""Saves the serialized auth token in the datastore.
|
||||
|
||||
The token is also stored in memcache to speed up retrieval on a cache hit.
|
||||
|
||||
Args:
|
||||
unique_key: The unique name for this token as a string. It is up to your
|
||||
code to ensure that this token value is unique in your application.
|
||||
Previous values will be silently overwitten.
|
||||
token_str: A serialized auth token as a string. I expect that this string
|
||||
will be generated by gdata.gauth.token_to_blob.
|
||||
|
||||
Returns:
|
||||
True if the token was stored sucessfully, False if the token could not be
|
||||
safely cached (if an old value could not be cleared). If the token was
|
||||
set in memcache, but not in the datastore, this function will return None.
|
||||
However, in that situation an exception will likely be raised.
|
||||
|
||||
Raises:
|
||||
Datastore exceptions may be raised from the App Engine SDK in the event of
|
||||
failure.
|
||||
"""
|
||||
# First try to save in memcache.
|
||||
result = memcache.set(unique_key, token_str)
|
||||
# If memcache fails to save the value, clear the cached value.
|
||||
if not result:
|
||||
result = memcache.delete(unique_key)
|
||||
# If we could not clear the cached value for this token, refuse to save.
|
||||
if result == 0:
|
||||
return False
|
||||
# Save to the datastore.
|
||||
if Token(key_name=unique_key, t=token_str).put():
|
||||
return True
|
||||
return None
|
||||
|
||||
|
||||
def delete_token(unique_key):
|
||||
# Clear from memcache.
|
||||
memcache.delete(unique_key)
|
||||
# Clear from the datastore.
|
||||
Token(key_name=unique_key).delete()
|
||||
@@ -1,321 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright (C) 2008 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
"""Provides HTTP functions for gdata.service to use on Google App Engine
|
||||
|
||||
AppEngineHttpClient: Provides an HTTP request method which uses App Engine's
|
||||
urlfetch API. Set the http_client member of a GDataService object to an
|
||||
instance of an AppEngineHttpClient to allow the gdata library to run on
|
||||
Google App Engine.
|
||||
|
||||
run_on_appengine: Function which will modify an existing GDataService object
|
||||
to allow it to run on App Engine. It works by creating a new instance of
|
||||
the AppEngineHttpClient and replacing the GDataService object's
|
||||
http_client.
|
||||
"""
|
||||
|
||||
|
||||
__author__ = 'api.jscudder (Jeff Scudder)'
|
||||
|
||||
|
||||
import StringIO
|
||||
import pickle
|
||||
import atom.http_interface
|
||||
import atom.token_store
|
||||
from google.appengine.api import urlfetch
|
||||
from google.appengine.ext import db
|
||||
from google.appengine.api import users
|
||||
from google.appengine.api import memcache
|
||||
|
||||
|
||||
def run_on_appengine(gdata_service, store_tokens=True,
|
||||
single_user_mode=False, deadline=None):
|
||||
"""Modifies a GDataService object to allow it to run on App Engine.
|
||||
|
||||
Args:
|
||||
gdata_service: An instance of AtomService, GDataService, or any
|
||||
of their subclasses which has an http_client member and a
|
||||
token_store member.
|
||||
store_tokens: Boolean, defaults to True. If True, the gdata_service
|
||||
will attempt to add each token to it's token_store when
|
||||
SetClientLoginToken or SetAuthSubToken is called. If False
|
||||
the tokens will not automatically be added to the
|
||||
token_store.
|
||||
single_user_mode: Boolean, defaults to False. If True, the current_token
|
||||
member of gdata_service will be set when
|
||||
SetClientLoginToken or SetAuthTubToken is called. If set
|
||||
to True, the current_token is set in the gdata_service
|
||||
and anyone who accesses the object will use the same
|
||||
token.
|
||||
|
||||
Note: If store_tokens is set to False and
|
||||
single_user_mode is set to False, all tokens will be
|
||||
ignored, since the library assumes: the tokens should not
|
||||
be stored in the datastore and they should not be stored
|
||||
in the gdata_service object. This will make it
|
||||
impossible to make requests which require authorization.
|
||||
deadline: int (optional) The number of seconds to wait for a response
|
||||
before timing out on the HTTP request. If no deadline is
|
||||
specified, the deafault deadline for HTTP requests from App
|
||||
Engine is used. The maximum is currently 10 (for 10 seconds).
|
||||
The default deadline for App Engine is 5 seconds.
|
||||
"""
|
||||
gdata_service.http_client = AppEngineHttpClient(deadline=deadline)
|
||||
gdata_service.token_store = AppEngineTokenStore()
|
||||
gdata_service.auto_store_tokens = store_tokens
|
||||
gdata_service.auto_set_current_token = single_user_mode
|
||||
return gdata_service
|
||||
|
||||
|
||||
class AppEngineHttpClient(atom.http_interface.GenericHttpClient):
|
||||
def __init__(self, headers=None, deadline=None):
|
||||
self.debug = False
|
||||
self.headers = headers or {}
|
||||
self.deadline = deadline
|
||||
|
||||
def request(self, operation, url, data=None, headers=None):
|
||||
"""Performs an HTTP call to the server, supports GET, POST, PUT, and
|
||||
DELETE.
|
||||
|
||||
Usage example, perform and HTTP GET on http://www.google.com/:
|
||||
import atom.http
|
||||
client = atom.http.HttpClient()
|
||||
http_response = client.request('GET', 'http://www.google.com/')
|
||||
|
||||
Args:
|
||||
operation: str The HTTP operation to be performed. This is usually one
|
||||
of 'GET', 'POST', 'PUT', or 'DELETE'
|
||||
data: filestream, list of parts, or other object which can be converted
|
||||
to a string. Should be set to None when performing a GET or DELETE.
|
||||
If data is a file-like object which can be read, this method will
|
||||
read a chunk of 100K bytes at a time and send them.
|
||||
If the data is a list of parts to be sent, each part will be
|
||||
evaluated and sent.
|
||||
url: The full URL to which the request should be sent. Can be a string
|
||||
or atom.url.Url.
|
||||
headers: dict of strings. HTTP headers which should be sent
|
||||
in the request.
|
||||
"""
|
||||
all_headers = self.headers.copy()
|
||||
if headers:
|
||||
all_headers.update(headers)
|
||||
|
||||
# Construct the full payload.
|
||||
# Assume that data is None or a string.
|
||||
data_str = data
|
||||
if data:
|
||||
if isinstance(data, list):
|
||||
# If data is a list of different objects, convert them all to strings
|
||||
# and join them together.
|
||||
converted_parts = [_convert_data_part(x) for x in data]
|
||||
data_str = ''.join(converted_parts)
|
||||
else:
|
||||
data_str = _convert_data_part(data)
|
||||
|
||||
# If the list of headers does not include a Content-Length, attempt to
|
||||
# calculate it based on the data object.
|
||||
if data and 'Content-Length' not in all_headers:
|
||||
all_headers['Content-Length'] = str(len(data_str))
|
||||
|
||||
# Set the content type to the default value if none was set.
|
||||
if 'Content-Type' not in all_headers:
|
||||
all_headers['Content-Type'] = 'application/atom+xml'
|
||||
|
||||
# Lookup the urlfetch operation which corresponds to the desired HTTP verb.
|
||||
if operation == 'GET':
|
||||
method = urlfetch.GET
|
||||
elif operation == 'POST':
|
||||
method = urlfetch.POST
|
||||
elif operation == 'PUT':
|
||||
method = urlfetch.PUT
|
||||
elif operation == 'DELETE':
|
||||
method = urlfetch.DELETE
|
||||
else:
|
||||
method = None
|
||||
if self.deadline is None:
|
||||
return HttpResponse(urlfetch.Fetch(url=str(url), payload=data_str,
|
||||
method=method, headers=all_headers, follow_redirects=False))
|
||||
return HttpResponse(urlfetch.Fetch(url=str(url), payload=data_str,
|
||||
method=method, headers=all_headers, follow_redirects=False,
|
||||
deadline=self.deadline))
|
||||
|
||||
|
||||
def _convert_data_part(data):
|
||||
if not data or isinstance(data, str):
|
||||
return data
|
||||
elif hasattr(data, 'read'):
|
||||
# data is a file like object, so read it completely.
|
||||
return data.read()
|
||||
# The data object was not a file.
|
||||
# Try to convert to a string and send the data.
|
||||
return str(data)
|
||||
|
||||
|
||||
class HttpResponse(object):
|
||||
"""Translates a urlfetch resoinse to look like an hhtplib resoinse.
|
||||
|
||||
Used to allow the resoinse from HttpRequest to be usable by gdata.service
|
||||
methods.
|
||||
"""
|
||||
|
||||
def __init__(self, urlfetch_response):
|
||||
self.body = StringIO.StringIO(urlfetch_response.content)
|
||||
self.headers = urlfetch_response.headers
|
||||
self.status = urlfetch_response.status_code
|
||||
self.reason = ''
|
||||
|
||||
def read(self, length=None):
|
||||
if not length:
|
||||
return self.body.read()
|
||||
else:
|
||||
return self.body.read(length)
|
||||
|
||||
def getheader(self, name):
|
||||
if not self.headers.has_key(name):
|
||||
return self.headers[name.lower()]
|
||||
return self.headers[name]
|
||||
|
||||
|
||||
class TokenCollection(db.Model):
|
||||
"""Datastore Model which associates auth tokens with the current user."""
|
||||
user = db.UserProperty()
|
||||
pickled_tokens = db.BlobProperty()
|
||||
|
||||
|
||||
class AppEngineTokenStore(atom.token_store.TokenStore):
|
||||
"""Stores the user's auth tokens in the App Engine datastore.
|
||||
|
||||
Tokens are only written to the datastore if a user is signed in (if
|
||||
users.get_current_user() returns a user object).
|
||||
"""
|
||||
def __init__(self):
|
||||
self.user = None
|
||||
|
||||
def add_token(self, token):
|
||||
"""Associates the token with the current user and stores it.
|
||||
|
||||
If there is no current user, the token will not be stored.
|
||||
|
||||
Returns:
|
||||
False if the token was not stored.
|
||||
"""
|
||||
tokens = load_auth_tokens(self.user)
|
||||
if not hasattr(token, 'scopes') or not token.scopes:
|
||||
return False
|
||||
for scope in token.scopes:
|
||||
tokens[str(scope)] = token
|
||||
key = save_auth_tokens(tokens, self.user)
|
||||
if key:
|
||||
return True
|
||||
return False
|
||||
|
||||
def find_token(self, url):
|
||||
"""Searches the current user's collection of token for a token which can
|
||||
be used for a request to the url.
|
||||
|
||||
Returns:
|
||||
The stored token which belongs to the current user and is valid for the
|
||||
desired URL. If there is no current user, or there is no valid user
|
||||
token in the datastore, a atom.http_interface.GenericToken is returned.
|
||||
"""
|
||||
if url is None:
|
||||
return None
|
||||
if isinstance(url, (str, unicode)):
|
||||
url = atom.url.parse_url(url)
|
||||
tokens = load_auth_tokens(self.user)
|
||||
if url in tokens:
|
||||
token = tokens[url]
|
||||
if token.valid_for_scope(url):
|
||||
return token
|
||||
else:
|
||||
del tokens[url]
|
||||
save_auth_tokens(tokens, self.user)
|
||||
for scope, token in tokens.iteritems():
|
||||
if token.valid_for_scope(url):
|
||||
return token
|
||||
return atom.http_interface.GenericToken()
|
||||
|
||||
def remove_token(self, token):
|
||||
"""Removes the token from the current user's collection in the datastore.
|
||||
|
||||
Returns:
|
||||
False if the token was not removed, this could be because the token was
|
||||
not in the datastore, or because there is no current user.
|
||||
"""
|
||||
token_found = False
|
||||
scopes_to_delete = []
|
||||
tokens = load_auth_tokens(self.user)
|
||||
for scope, stored_token in tokens.iteritems():
|
||||
if stored_token == token:
|
||||
scopes_to_delete.append(scope)
|
||||
token_found = True
|
||||
for scope in scopes_to_delete:
|
||||
del tokens[scope]
|
||||
if token_found:
|
||||
save_auth_tokens(tokens, self.user)
|
||||
return token_found
|
||||
|
||||
def remove_all_tokens(self):
|
||||
"""Removes all of the current user's tokens from the datastore."""
|
||||
save_auth_tokens({}, self.user)
|
||||
|
||||
|
||||
def save_auth_tokens(token_dict, user=None):
|
||||
"""Associates the tokens with the current user and writes to the datastore.
|
||||
|
||||
If there us no current user, the tokens are not written and this function
|
||||
returns None.
|
||||
|
||||
Returns:
|
||||
The key of the datastore entity containing the user's tokens, or None if
|
||||
there was no current user.
|
||||
"""
|
||||
if user is None:
|
||||
user = users.get_current_user()
|
||||
if user is None:
|
||||
return None
|
||||
memcache.set('gdata_pickled_tokens:%s' % user, pickle.dumps(token_dict))
|
||||
user_tokens = TokenCollection.all().filter('user =', user).get()
|
||||
if user_tokens:
|
||||
user_tokens.pickled_tokens = pickle.dumps(token_dict)
|
||||
return user_tokens.put()
|
||||
else:
|
||||
user_tokens = TokenCollection(
|
||||
user=user,
|
||||
pickled_tokens=pickle.dumps(token_dict))
|
||||
return user_tokens.put()
|
||||
|
||||
|
||||
def load_auth_tokens(user=None):
|
||||
"""Reads a dictionary of the current user's tokens from the datastore.
|
||||
|
||||
If there is no current user (a user is not signed in to the app) or the user
|
||||
does not have any tokens, an empty dictionary is returned.
|
||||
"""
|
||||
if user is None:
|
||||
user = users.get_current_user()
|
||||
if user is None:
|
||||
return {}
|
||||
pickled_tokens = memcache.get('gdata_pickled_tokens:%s' % user)
|
||||
if pickled_tokens:
|
||||
return pickle.loads(pickled_tokens)
|
||||
user_tokens = TokenCollection.all().filter('user =', user).get()
|
||||
if user_tokens:
|
||||
memcache.set('gdata_pickled_tokens:%s' % user, user_tokens.pickled_tokens)
|
||||
return pickle.loads(user_tokens.pickled_tokens)
|
||||
return {}
|
||||
|
||||
@@ -1,526 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright (C) 2007 SIOS Technology, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Contains objects used with Google Apps."""
|
||||
|
||||
__author__ = 'tmatsuo@sios.com (Takashi MATSUO)'
|
||||
|
||||
|
||||
import atom
|
||||
import gdata
|
||||
|
||||
|
||||
# XML namespaces which are often used in Google Apps entity.
|
||||
APPS_NAMESPACE = 'http://schemas.google.com/apps/2006'
|
||||
APPS_TEMPLATE = '{http://schemas.google.com/apps/2006}%s'
|
||||
|
||||
|
||||
class EmailList(atom.AtomBase):
|
||||
"""The Google Apps EmailList element"""
|
||||
|
||||
_tag = 'emailList'
|
||||
_namespace = APPS_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['name'] = 'name'
|
||||
|
||||
def __init__(self, name=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
self.name = name
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
def EmailListFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(EmailList, xml_string)
|
||||
|
||||
|
||||
class Who(atom.AtomBase):
|
||||
"""The Google Apps Who element"""
|
||||
|
||||
_tag = 'who'
|
||||
_namespace = gdata.GDATA_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['rel'] = 'rel'
|
||||
_attributes['email'] = 'email'
|
||||
|
||||
def __init__(self, rel=None, email=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
self.rel = rel
|
||||
self.email = email
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
def WhoFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(Who, xml_string)
|
||||
|
||||
|
||||
class Login(atom.AtomBase):
|
||||
"""The Google Apps Login element"""
|
||||
|
||||
_tag = 'login'
|
||||
_namespace = APPS_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['userName'] = 'user_name'
|
||||
_attributes['password'] = 'password'
|
||||
_attributes['suspended'] = 'suspended'
|
||||
_attributes['admin'] = 'admin'
|
||||
_attributes['changePasswordAtNextLogin'] = 'change_password'
|
||||
_attributes['agreedToTerms'] = 'agreed_to_terms'
|
||||
_attributes['ipWhitelisted'] = 'ip_whitelisted'
|
||||
_attributes['hashFunctionName'] = 'hash_function_name'
|
||||
|
||||
def __init__(self, user_name=None, password=None, suspended=None,
|
||||
ip_whitelisted=None, hash_function_name=None,
|
||||
admin=None, change_password=None, agreed_to_terms=None,
|
||||
extension_elements=None, extension_attributes=None,
|
||||
text=None):
|
||||
self.user_name = user_name
|
||||
self.password = password
|
||||
self.suspended = suspended
|
||||
self.admin = admin
|
||||
self.change_password = change_password
|
||||
self.agreed_to_terms = agreed_to_terms
|
||||
self.ip_whitelisted = ip_whitelisted
|
||||
self.hash_function_name = hash_function_name
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def LoginFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(Login, xml_string)
|
||||
|
||||
|
||||
class Quota(atom.AtomBase):
|
||||
"""The Google Apps Quota element"""
|
||||
|
||||
_tag = 'quota'
|
||||
_namespace = APPS_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['limit'] = 'limit'
|
||||
|
||||
def __init__(self, limit=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
self.limit = limit
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def QuotaFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(Quota, xml_string)
|
||||
|
||||
|
||||
class Name(atom.AtomBase):
|
||||
"""The Google Apps Name element"""
|
||||
|
||||
_tag = 'name'
|
||||
_namespace = APPS_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['familyName'] = 'family_name'
|
||||
_attributes['givenName'] = 'given_name'
|
||||
|
||||
def __init__(self, family_name=None, given_name=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
self.family_name = family_name
|
||||
self.given_name = given_name
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def NameFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(Name, xml_string)
|
||||
|
||||
|
||||
class Nickname(atom.AtomBase):
|
||||
"""The Google Apps Nickname element"""
|
||||
|
||||
_tag = 'nickname'
|
||||
_namespace = APPS_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['name'] = 'name'
|
||||
|
||||
def __init__(self, name=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
self.name = name
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def NicknameFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(Nickname, xml_string)
|
||||
|
||||
|
||||
class NicknameEntry(gdata.GDataEntry):
|
||||
"""A Google Apps flavor of an Atom Entry for Nickname"""
|
||||
|
||||
_tag = 'entry'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.GDataEntry._children.copy()
|
||||
_attributes = gdata.GDataEntry._attributes.copy()
|
||||
_children['{%s}login' % APPS_NAMESPACE] = ('login', Login)
|
||||
_children['{%s}nickname' % APPS_NAMESPACE] = ('nickname', Nickname)
|
||||
|
||||
def __init__(self, author=None, category=None, content=None,
|
||||
atom_id=None, link=None, published=None,
|
||||
title=None, updated=None,
|
||||
login=None, nickname=None,
|
||||
extended_property=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
|
||||
gdata.GDataEntry.__init__(self, author=author, category=category,
|
||||
content=content,
|
||||
atom_id=atom_id, link=link, published=published,
|
||||
title=title, updated=updated)
|
||||
self.login = login
|
||||
self.nickname = nickname
|
||||
self.extended_property = extended_property or []
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def NicknameEntryFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(NicknameEntry, xml_string)
|
||||
|
||||
|
||||
class NicknameFeed(gdata.GDataFeed, gdata.LinkFinder):
|
||||
"""A Google Apps Nickname feed flavor of an Atom Feed"""
|
||||
|
||||
_tag = 'feed'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.GDataFeed._children.copy()
|
||||
_attributes = gdata.GDataFeed._attributes.copy()
|
||||
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [NicknameEntry])
|
||||
|
||||
def __init__(self, author=None, category=None, contributor=None,
|
||||
generator=None, icon=None, atom_id=None, link=None, logo=None,
|
||||
rights=None, subtitle=None, title=None, updated=None,
|
||||
entry=None, total_results=None, start_index=None,
|
||||
items_per_page=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
gdata.GDataFeed.__init__(self, author=author, category=category,
|
||||
contributor=contributor, generator=generator,
|
||||
icon=icon, atom_id=atom_id, link=link,
|
||||
logo=logo, rights=rights, subtitle=subtitle,
|
||||
title=title, updated=updated, entry=entry,
|
||||
total_results=total_results,
|
||||
start_index=start_index,
|
||||
items_per_page=items_per_page,
|
||||
extension_elements=extension_elements,
|
||||
extension_attributes=extension_attributes,
|
||||
text=text)
|
||||
|
||||
|
||||
def NicknameFeedFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(NicknameFeed, xml_string)
|
||||
|
||||
|
||||
class UserEntry(gdata.GDataEntry):
|
||||
"""A Google Apps flavor of an Atom Entry"""
|
||||
|
||||
_tag = 'entry'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.GDataEntry._children.copy()
|
||||
_attributes = gdata.GDataEntry._attributes.copy()
|
||||
_children['{%s}login' % APPS_NAMESPACE] = ('login', Login)
|
||||
_children['{%s}name' % APPS_NAMESPACE] = ('name', Name)
|
||||
_children['{%s}quota' % APPS_NAMESPACE] = ('quota', Quota)
|
||||
# This child may already be defined in GDataEntry, confirm before removing.
|
||||
_children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link',
|
||||
[gdata.FeedLink])
|
||||
_children['{%s}who' % gdata.GDATA_NAMESPACE] = ('who', Who)
|
||||
|
||||
def __init__(self, author=None, category=None, content=None,
|
||||
atom_id=None, link=None, published=None,
|
||||
title=None, updated=None,
|
||||
login=None, name=None, quota=None, who=None, feed_link=None,
|
||||
extended_property=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
|
||||
gdata.GDataEntry.__init__(self, author=author, category=category,
|
||||
content=content,
|
||||
atom_id=atom_id, link=link, published=published,
|
||||
title=title, updated=updated)
|
||||
self.login = login
|
||||
self.name = name
|
||||
self.quota = quota
|
||||
self.who = who
|
||||
self.feed_link = feed_link or []
|
||||
self.extended_property = extended_property or []
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def UserEntryFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(UserEntry, xml_string)
|
||||
|
||||
|
||||
class UserFeed(gdata.GDataFeed, gdata.LinkFinder):
|
||||
"""A Google Apps User feed flavor of an Atom Feed"""
|
||||
|
||||
_tag = 'feed'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.GDataFeed._children.copy()
|
||||
_attributes = gdata.GDataFeed._attributes.copy()
|
||||
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [UserEntry])
|
||||
|
||||
def __init__(self, author=None, category=None, contributor=None,
|
||||
generator=None, icon=None, atom_id=None, link=None, logo=None,
|
||||
rights=None, subtitle=None, title=None, updated=None,
|
||||
entry=None, total_results=None, start_index=None,
|
||||
items_per_page=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
gdata.GDataFeed.__init__(self, author=author, category=category,
|
||||
contributor=contributor, generator=generator,
|
||||
icon=icon, atom_id=atom_id, link=link,
|
||||
logo=logo, rights=rights, subtitle=subtitle,
|
||||
title=title, updated=updated, entry=entry,
|
||||
total_results=total_results,
|
||||
start_index=start_index,
|
||||
items_per_page=items_per_page,
|
||||
extension_elements=extension_elements,
|
||||
extension_attributes=extension_attributes,
|
||||
text=text)
|
||||
|
||||
|
||||
def UserFeedFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(UserFeed, xml_string)
|
||||
|
||||
|
||||
class EmailListEntry(gdata.GDataEntry):
|
||||
"""A Google Apps EmailList flavor of an Atom Entry"""
|
||||
|
||||
_tag = 'entry'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.GDataEntry._children.copy()
|
||||
_attributes = gdata.GDataEntry._attributes.copy()
|
||||
_children['{%s}emailList' % APPS_NAMESPACE] = ('email_list', EmailList)
|
||||
# Might be able to remove this _children entry.
|
||||
_children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link',
|
||||
[gdata.FeedLink])
|
||||
|
||||
def __init__(self, author=None, category=None, content=None,
|
||||
atom_id=None, link=None, published=None,
|
||||
title=None, updated=None,
|
||||
email_list=None, feed_link=None,
|
||||
extended_property=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
|
||||
gdata.GDataEntry.__init__(self, author=author, category=category,
|
||||
content=content,
|
||||
atom_id=atom_id, link=link, published=published,
|
||||
title=title, updated=updated)
|
||||
self.email_list = email_list
|
||||
self.feed_link = feed_link or []
|
||||
self.extended_property = extended_property or []
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def EmailListEntryFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(EmailListEntry, xml_string)
|
||||
|
||||
|
||||
class EmailListFeed(gdata.GDataFeed, gdata.LinkFinder):
|
||||
"""A Google Apps EmailList feed flavor of an Atom Feed"""
|
||||
|
||||
_tag = 'feed'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.GDataFeed._children.copy()
|
||||
_attributes = gdata.GDataFeed._attributes.copy()
|
||||
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [EmailListEntry])
|
||||
|
||||
def __init__(self, author=None, category=None, contributor=None,
|
||||
generator=None, icon=None, atom_id=None, link=None, logo=None,
|
||||
rights=None, subtitle=None, title=None, updated=None,
|
||||
entry=None, total_results=None, start_index=None,
|
||||
items_per_page=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
gdata.GDataFeed.__init__(self, author=author, category=category,
|
||||
contributor=contributor, generator=generator,
|
||||
icon=icon, atom_id=atom_id, link=link,
|
||||
logo=logo, rights=rights, subtitle=subtitle,
|
||||
title=title, updated=updated, entry=entry,
|
||||
total_results=total_results,
|
||||
start_index=start_index,
|
||||
items_per_page=items_per_page,
|
||||
extension_elements=extension_elements,
|
||||
extension_attributes=extension_attributes,
|
||||
text=text)
|
||||
|
||||
|
||||
def EmailListFeedFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(EmailListFeed, xml_string)
|
||||
|
||||
|
||||
class EmailListRecipientEntry(gdata.GDataEntry):
|
||||
"""A Google Apps EmailListRecipient flavor of an Atom Entry"""
|
||||
|
||||
_tag = 'entry'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.GDataEntry._children.copy()
|
||||
_attributes = gdata.GDataEntry._attributes.copy()
|
||||
_children['{%s}who' % gdata.GDATA_NAMESPACE] = ('who', Who)
|
||||
|
||||
def __init__(self, author=None, category=None, content=None,
|
||||
atom_id=None, link=None, published=None,
|
||||
title=None, updated=None,
|
||||
who=None,
|
||||
extended_property=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
|
||||
gdata.GDataEntry.__init__(self, author=author, category=category,
|
||||
content=content,
|
||||
atom_id=atom_id, link=link, published=published,
|
||||
title=title, updated=updated)
|
||||
self.who = who
|
||||
self.extended_property = extended_property or []
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def EmailListRecipientEntryFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(EmailListRecipientEntry, xml_string)
|
||||
|
||||
|
||||
class EmailListRecipientFeed(gdata.GDataFeed, gdata.LinkFinder):
|
||||
"""A Google Apps EmailListRecipient feed flavor of an Atom Feed"""
|
||||
|
||||
_tag = 'feed'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.GDataFeed._children.copy()
|
||||
_attributes = gdata.GDataFeed._attributes.copy()
|
||||
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry',
|
||||
[EmailListRecipientEntry])
|
||||
|
||||
def __init__(self, author=None, category=None, contributor=None,
|
||||
generator=None, icon=None, atom_id=None, link=None, logo=None,
|
||||
rights=None, subtitle=None, title=None, updated=None,
|
||||
entry=None, total_results=None, start_index=None,
|
||||
items_per_page=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
gdata.GDataFeed.__init__(self, author=author, category=category,
|
||||
contributor=contributor, generator=generator,
|
||||
icon=icon, atom_id=atom_id, link=link,
|
||||
logo=logo, rights=rights, subtitle=subtitle,
|
||||
title=title, updated=updated, entry=entry,
|
||||
total_results=total_results,
|
||||
start_index=start_index,
|
||||
items_per_page=items_per_page,
|
||||
extension_elements=extension_elements,
|
||||
extension_attributes=extension_attributes,
|
||||
text=text)
|
||||
|
||||
|
||||
def EmailListRecipientFeedFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(EmailListRecipientFeed, xml_string)
|
||||
|
||||
|
||||
class Property(atom.AtomBase):
|
||||
"""The Google Apps Property element"""
|
||||
|
||||
_tag = 'property'
|
||||
_namespace = APPS_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['name'] = 'name'
|
||||
_attributes['value'] = 'value'
|
||||
|
||||
def __init__(self, name=None, value=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
self.name = name
|
||||
self.value = value
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def PropertyFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(Property, xml_string)
|
||||
|
||||
|
||||
class PropertyEntry(gdata.GDataEntry):
|
||||
"""A Google Apps Property flavor of an Atom Entry"""
|
||||
|
||||
_tag = 'entry'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.GDataEntry._children.copy()
|
||||
_attributes = gdata.GDataEntry._attributes.copy()
|
||||
_children['{%s}property' % APPS_NAMESPACE] = ('property', [Property])
|
||||
|
||||
def __init__(self, author=None, category=None, content=None,
|
||||
atom_id=None, link=None, published=None,
|
||||
title=None, updated=None,
|
||||
property=None,
|
||||
extended_property=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
|
||||
gdata.GDataEntry.__init__(self, author=author, category=category,
|
||||
content=content,
|
||||
atom_id=atom_id, link=link, published=published,
|
||||
title=title, updated=updated)
|
||||
self.property = property
|
||||
self.extended_property = extended_property or []
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def PropertyEntryFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(PropertyEntry, xml_string)
|
||||
|
||||
class PropertyFeed(gdata.GDataFeed, gdata.LinkFinder):
|
||||
"""A Google Apps Property feed flavor of an Atom Feed"""
|
||||
|
||||
_tag = 'feed'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.GDataFeed._children.copy()
|
||||
_attributes = gdata.GDataFeed._attributes.copy()
|
||||
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [PropertyEntry])
|
||||
|
||||
def __init__(self, author=None, category=None, contributor=None,
|
||||
generator=None, icon=None, atom_id=None, link=None, logo=None,
|
||||
rights=None, subtitle=None, title=None, updated=None,
|
||||
entry=None, total_results=None, start_index=None,
|
||||
items_per_page=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
gdata.GDataFeed.__init__(self, author=author, category=category,
|
||||
contributor=contributor, generator=generator,
|
||||
icon=icon, atom_id=atom_id, link=link,
|
||||
logo=logo, rights=rights, subtitle=subtitle,
|
||||
title=title, updated=updated, entry=entry,
|
||||
total_results=total_results,
|
||||
start_index=start_index,
|
||||
items_per_page=items_per_page,
|
||||
extension_elements=extension_elements,
|
||||
extension_attributes=extension_attributes,
|
||||
text=text)
|
||||
|
||||
def PropertyFeedFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(PropertyFeed, xml_string)
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,278 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (C) 2008 Google, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Allow Google Apps domain administrators to audit user data.
|
||||
|
||||
AuditService: Set auditing."""
|
||||
|
||||
__author__ = 'jlee@pbu.edu'
|
||||
|
||||
from base64 import b64encode
|
||||
|
||||
import gdata.apps
|
||||
import gdata.apps.service
|
||||
import gdata.service
|
||||
|
||||
class AuditService(gdata.apps.service.PropertyService):
|
||||
"""Client for the Google Apps Audit service."""
|
||||
|
||||
def _serviceUrl(self, setting_id, domain=None, user=None):
|
||||
if domain is None:
|
||||
domain = self.domain
|
||||
if user is None:
|
||||
return '/a/feeds/compliance/audit/%s/%s' % (setting_id, domain)
|
||||
else:
|
||||
return '/a/feeds/compliance/audit/%s/%s/%s' % (setting_id, domain, user)
|
||||
|
||||
def updatePGPKey(self, pgpkey):
|
||||
"""Updates Public PGP Key Google uses to encrypt audit data
|
||||
|
||||
Args:
|
||||
pgpkey: string, ASCII text of PGP Public Key to be used
|
||||
|
||||
Returns:
|
||||
A dict containing the result of the POST operation."""
|
||||
|
||||
uri = self._serviceUrl('publickey')
|
||||
b64pgpkey = b64encode(pgpkey)
|
||||
properties = {}
|
||||
properties['publicKey'] = b64pgpkey
|
||||
return self._PostProperties(uri, properties)
|
||||
|
||||
def createEmailMonitor(self, source_user, destination_user, end_date,
|
||||
begin_date=None, incoming_headers_only=False,
|
||||
outgoing_headers_only=False, drafts=False,
|
||||
drafts_headers_only=False, chats=False,
|
||||
chats_headers_only=False):
|
||||
"""Creates a email monitor, forwarding the source_users emails/chats
|
||||
|
||||
Args:
|
||||
source_user: string, the user whose email will be audited
|
||||
destination_user: string, the user to receive the audited email
|
||||
end_date: string, the date the audit will end in
|
||||
"yyyy-MM-dd HH:mm" format, required
|
||||
begin_date: string, the date the audit will start in
|
||||
"yyyy-MM-dd HH:mm" format, leave blank to use current time
|
||||
incoming_headers_only: boolean, whether to audit only the headers of
|
||||
mail delivered to source user
|
||||
outgoing_headers_only: boolean, whether to audit only the headers of
|
||||
mail sent from the source user
|
||||
drafts: boolean, whether to audit draft messages of the source user
|
||||
drafts_headers_only: boolean, whether to audit only the headers of
|
||||
mail drafts saved by the user
|
||||
chats: boolean, whether to audit archived chats of the source user
|
||||
chats_headers_only: boolean, whether to audit only the headers of
|
||||
archived chats of the source user
|
||||
|
||||
Returns:
|
||||
A dict containing the result of the POST operation."""
|
||||
|
||||
uri = self._serviceUrl('mail/monitor', user=source_user)
|
||||
properties = {}
|
||||
properties['destUserName'] = destination_user
|
||||
if begin_date is not None:
|
||||
properties['beginDate'] = begin_date
|
||||
properties['endDate'] = end_date
|
||||
if incoming_headers_only:
|
||||
properties['incomingEmailMonitorLevel'] = 'HEADER_ONLY'
|
||||
else:
|
||||
properties['incomingEmailMonitorLevel'] = 'FULL_MESSAGE'
|
||||
if outgoing_headers_only:
|
||||
properties['outgoingEmailMonitorLevel'] = 'HEADER_ONLY'
|
||||
else:
|
||||
properties['outgoingEmailMonitorLevel'] = 'FULL_MESSAGE'
|
||||
if drafts:
|
||||
if drafts_headers_only:
|
||||
properties['draftMonitorLevel'] = 'HEADER_ONLY'
|
||||
else:
|
||||
properties['draftMonitorLevel'] = 'FULL_MESSAGE'
|
||||
if chats:
|
||||
if chats_headers_only:
|
||||
properties['chatMonitorLevel'] = 'HEADER_ONLY'
|
||||
else:
|
||||
properties['chatMonitorLevel'] = 'FULL_MESSAGE'
|
||||
return self._PostProperties(uri, properties)
|
||||
|
||||
def getEmailMonitors(self, user):
|
||||
""""Gets the email monitors for the given user
|
||||
|
||||
Args:
|
||||
user: string, the user to retrieve email monitors for
|
||||
|
||||
Returns:
|
||||
list results of the POST operation
|
||||
|
||||
"""
|
||||
uri = self._serviceUrl('mail/monitor', user=user)
|
||||
return self._GetPropertiesList(uri)
|
||||
|
||||
def deleteEmailMonitor(self, source_user, destination_user):
|
||||
"""Deletes the email monitor for the given user
|
||||
|
||||
Args:
|
||||
source_user: string, the user who is being monitored
|
||||
destination_user: string, theuser who recieves the monitored emails
|
||||
|
||||
Returns:
|
||||
Nothing
|
||||
"""
|
||||
|
||||
uri = self._serviceUrl('mail/monitor', user=source_user+'/'+destination_user)
|
||||
try:
|
||||
return self._DeleteProperties(uri)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def createAccountInformationRequest(self, user):
|
||||
"""Creates a request for account auditing details
|
||||
|
||||
Args:
|
||||
user: string, the user to request account information for
|
||||
|
||||
Returns:
|
||||
A dict containing the result of the post operation."""
|
||||
|
||||
uri = self._serviceUrl('account', user=user)
|
||||
properties = {}
|
||||
#XML Body is left empty
|
||||
try:
|
||||
return self._PostProperties(uri, properties)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def getAccountInformationRequestStatus(self, user, request_id):
|
||||
"""Gets the status of an account auditing request
|
||||
|
||||
Args:
|
||||
user: string, the user whose account auditing details were requested
|
||||
request_id: string, the request_id
|
||||
|
||||
Returns:
|
||||
A dict containing the result of the get operation."""
|
||||
|
||||
uri = self._serviceUrl('account', user=user+'/'+request_id)
|
||||
try:
|
||||
return self._GetProperties(uri)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def getAllAccountInformationRequestsStatus(self):
|
||||
"""Gets the status of all account auditing requests for the domain
|
||||
|
||||
Args:
|
||||
None
|
||||
|
||||
Returns:
|
||||
list results of the POST operation
|
||||
"""
|
||||
|
||||
uri = self._serviceUrl('account')
|
||||
return self._GetPropertiesList(uri)
|
||||
|
||||
|
||||
def deleteAccountInformationRequest(self, user, request_id):
|
||||
"""Deletes the request for account auditing information
|
||||
|
||||
Args:
|
||||
user: string, the user whose account auditing details were requested
|
||||
request_id: string, the request_id
|
||||
|
||||
Returns:
|
||||
Nothing
|
||||
"""
|
||||
|
||||
uri = self._serviceUrl('account', user=user+'/'+request_id)
|
||||
try:
|
||||
return self._DeleteProperties(uri)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def createMailboxExportRequest(self, user, begin_date=None, end_date=None, include_deleted=False, search_query=None, headers_only=False):
|
||||
"""Creates a mailbox export request
|
||||
|
||||
Args:
|
||||
user: string, the user whose mailbox export is being requested
|
||||
begin_date: string, date of earliest emails to export, optional, defaults to date of account creation
|
||||
format is 'yyyy-MM-dd HH:mm'
|
||||
end_date: string, date of latest emails to export, optional, defaults to current date
|
||||
format is 'yyyy-MM-dd HH:mm'
|
||||
include_deleted: boolean, whether to include deleted emails in export, mutually exclusive with search_query
|
||||
search_query: string, gmail style search query, matched emails will be exported, mutually exclusive with include_deleted
|
||||
|
||||
Returns:
|
||||
A dict containing the result of the post operation."""
|
||||
|
||||
uri = self._serviceUrl('mail/export', user=user)
|
||||
properties = {}
|
||||
if begin_date is not None:
|
||||
properties['beginDate'] = begin_date
|
||||
if end_date is not None:
|
||||
properties['endDate'] = end_date
|
||||
if include_deleted is not None:
|
||||
properties['includeDeleted'] = gdata.apps.service._bool2str(include_deleted)
|
||||
if search_query is not None:
|
||||
properties['searchQuery'] = search_query
|
||||
if headers_only is True:
|
||||
properties['packageContent'] = 'HEADER_ONLY'
|
||||
else:
|
||||
properties['packageContent'] = 'FULL_MESSAGE'
|
||||
return self._PostProperties(uri, properties)
|
||||
|
||||
def getMailboxExportRequestStatus(self, user, request_id):
|
||||
"""Gets the status of an mailbox export request
|
||||
|
||||
Args:
|
||||
user: string, the user whose mailbox were requested
|
||||
request_id: string, the request_id
|
||||
|
||||
Returns:
|
||||
A dict containing the result of the get operation."""
|
||||
|
||||
uri = self._serviceUrl('mail/export', user=user+'/'+request_id)
|
||||
try:
|
||||
return self._GetProperties(uri)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def getAllMailboxExportRequestsStatus(self):
|
||||
"""Gets the status of all mailbox export requests for the domain
|
||||
|
||||
Args:
|
||||
None
|
||||
|
||||
Returns:
|
||||
list results of the POST operation
|
||||
"""
|
||||
|
||||
uri = self._serviceUrl('mail/export')
|
||||
return self._GetPropertiesList(uri)
|
||||
|
||||
|
||||
def deleteMailboxExportRequest(self, user, request_id):
|
||||
"""Deletes the request for mailbox export
|
||||
|
||||
Args:
|
||||
user: string, the user whose mailbox were requested
|
||||
request_id: string, the request_id
|
||||
|
||||
Returns:
|
||||
Nothing
|
||||
"""
|
||||
|
||||
uri = self._serviceUrl('mail/export', user=user+'/'+request_id)
|
||||
try:
|
||||
return self._DeleteProperties(uri)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
@@ -1,874 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2009 Google Inc. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Data model classes for parsing and generating XML for the Contacts API."""
|
||||
|
||||
import atom
|
||||
import gdata
|
||||
|
||||
## Constants from http://code.google.com/apis/gdata/elements.html ##
|
||||
REL_HOME = 'http://schemas.google.com/g/2005#home'
|
||||
REL_WORK = 'http://schemas.google.com/g/2005#work'
|
||||
REL_OTHER = 'http://schemas.google.com/g/2005#other'
|
||||
|
||||
IM_AIM = 'http://schemas.google.com/g/2005#AIM' # AOL Instant Messenger protocol
|
||||
IM_MSN = 'http://schemas.google.com/g/2005#MSN' # MSN Messenger protocol
|
||||
IM_YAHOO = 'http://schemas.google.com/g/2005#YAHOO' # Yahoo Messenger protocol
|
||||
IM_SKYPE = 'http://schemas.google.com/g/2005#SKYPE' # Skype protocol
|
||||
IM_QQ = 'http://schemas.google.com/g/2005#QQ' # QQ protocol
|
||||
IM_GOOGLE_TALK = 'http://schemas.google.com/g/2005#GOOGLE_TALK' # Google Talk protocol
|
||||
IM_ICQ = 'http://schemas.google.com/g/2005#ICQ' # ICQ protocol
|
||||
IM_JABBER = 'http://schemas.google.com/g/2005#JABBER' # Jabber protocol
|
||||
IM_NETMEETING = 'http://schemas.google.com/g/2005#NETMEETING' # NetMeeting
|
||||
|
||||
PHOTO_LINK_REL = 'http://schemas.google.com/contacts/2008/rel#photo'
|
||||
PHOTO_EDIT_LINK_REL = 'http://schemas.google.com/contacts/2008/rel#edit-photo'
|
||||
|
||||
# Different phone types, for more info see:
|
||||
# http://code.google.com/apis/gdata/docs/2.0/elements.html#gdPhoneNumber
|
||||
PHONE_ASSISTANT = 'http://schemas.google.com/g/2005#assistant'
|
||||
PHONE_CALLBACK = 'http://schemas.google.com/g/2005#callback'
|
||||
PHONE_CAR = 'http://schemas.google.com/g/2005#car'
|
||||
PHONE_COMPANY_MAIN = 'http://schemas.google.com/g/2005#company_main'
|
||||
PHONE_FAX = 'http://schemas.google.com/g/2005#fax'
|
||||
PHONE_GENERAL = 'http://schemas.google.com/g/2005#general'
|
||||
PHONE_HOME = REL_HOME
|
||||
PHONE_HOME_FAX = 'http://schemas.google.com/g/2005#home_fax'
|
||||
PHONE_INTERNAL = 'http://schemas.google.com/g/2005#internal-extension'
|
||||
PHONE_ISDN = 'http://schemas.google.com/g/2005#isdn'
|
||||
PHONE_MAIN = 'http://schemas.google.com/g/2005#main'
|
||||
PHONE_MOBILE = 'http://schemas.google.com/g/2005#mobile'
|
||||
PHONE_OTHER = REL_OTHER
|
||||
PHONE_OTHER_FAX = 'http://schemas.google.com/g/2005#other_fax'
|
||||
PHONE_PAGER = 'http://schemas.google.com/g/2005#pager'
|
||||
PHONE_RADIO = 'http://schemas.google.com/g/2005#radio'
|
||||
PHONE_SATELLITE = 'http://schemas.google.com/g/2005#satellite'
|
||||
PHONE_TELEX = 'http://schemas.google.com/g/2005#telex'
|
||||
PHONE_TTY_TDD = 'http://schemas.google.com/g/2005#tty_tdd'
|
||||
PHONE_VOIP = 'http://schemas.google.com/g/2005#voip'
|
||||
PHONE_WORK = REL_WORK
|
||||
PHONE_WORK_FAX = 'http://schemas.google.com/g/2005#work_fax'
|
||||
PHONE_WORK_MOBILE = 'http://schemas.google.com/g/2005#work_mobile'
|
||||
PHONE_WORK_PAGER = 'http://schemas.google.com/g/2005#work_pager'
|
||||
|
||||
MAIL_BOTH = 'http://schemas.google.com/g/2005#both'
|
||||
MAIL_LETTERS = 'http://schemas.google.com/g/2005#letters'
|
||||
MAIL_PARCELS = 'http://schemas.google.com/g/2005#parcels'
|
||||
MAIL_NEITHER = 'http://schemas.google.com/g/2005#neither'
|
||||
|
||||
GENERAL_ADDRESS = 'http://schemas.google.com/g/2005#general'
|
||||
LOCAL_ADDRESS = 'http://schemas.google.com/g/2005#local'
|
||||
|
||||
EXTERNAL_ID_ORGANIZATION = 'organization'
|
||||
|
||||
RELATION_MANAGER = 'manager'
|
||||
|
||||
CONTACTS_NAMESPACE = 'http://schemas.google.com/contact/2008'
|
||||
|
||||
|
||||
class GDataBase(atom.AtomBase):
|
||||
"""The Google Contacts intermediate class from atom.AtomBase."""
|
||||
_namespace = gdata.GDATA_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
|
||||
def __init__(self, text=None):
|
||||
atom.AtomBase.__init__(self, text=text)
|
||||
|
||||
class ContactsBase(GDataBase):
|
||||
"""The Google Contacts intermediate class for Contacts namespace."""
|
||||
_namespace = CONTACTS_NAMESPACE
|
||||
|
||||
class BillingInformation(ContactsBase):
|
||||
"""The gContact:billingInformation element."""
|
||||
_tag = 'billingInformation'
|
||||
|
||||
class Birthday(ContactsBase):
|
||||
"""The gContact:birthday element."""
|
||||
_tag = 'birthday'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['when'] = 'when'
|
||||
|
||||
def __init__(self, when=None):
|
||||
ContactsBase.__init__(self)
|
||||
self.when = when
|
||||
|
||||
class CalendarLink(ContactsBase):
|
||||
"""The gContact:calendarLink element."""
|
||||
_tag = 'calendarLink'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['href'] = 'href'
|
||||
_attributes['label'] = 'label'
|
||||
_attributes['primary'] = 'primary'
|
||||
_attributes['rel'] = 'rel'
|
||||
|
||||
def __init__(self, href=None, label=None, primary='false', rel=None):
|
||||
ContactsBase.__init__(self)
|
||||
self.href = href
|
||||
self.label = label
|
||||
self.primary = primary
|
||||
self.rel = rel
|
||||
|
||||
class Content(atom.AtomBase):
|
||||
"""The Google Contacts Content element."""
|
||||
_tag = 'content'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
|
||||
def __init__(self, text=None):
|
||||
atom.AtomBase.__init__(self, text=text)
|
||||
|
||||
class DirectoryServer(ContactsBase):
|
||||
"""The gContact:directoryServer element."""
|
||||
_tag = 'directoryServer'
|
||||
|
||||
class Email(GDataBase):
|
||||
"""The gd:email element."""
|
||||
_tag = 'email'
|
||||
_children = GDataBase._children.copy()
|
||||
_attributes = GDataBase._attributes.copy()
|
||||
_attributes['address'] = 'address'
|
||||
_attributes['primary'] = 'primary'
|
||||
_attributes['rel'] = 'rel'
|
||||
_attributes['label'] = 'label'
|
||||
|
||||
def __init__(self, label=None, rel=None, address=None, primary='false'):
|
||||
GDataBase.__init__(self)
|
||||
self.label = label
|
||||
self.rel = rel
|
||||
self.address = address
|
||||
self.primary = primary
|
||||
|
||||
class When(GDataBase):
|
||||
"""The Google Contacts when element."""
|
||||
_tag = 'when'
|
||||
_children = GDataBase._children.copy()
|
||||
_attributes = GDataBase._attributes.copy()
|
||||
_attributes['startTime'] = 'startTime'
|
||||
_attributes['label'] = 'label'
|
||||
|
||||
def __init__(self, startTime=None, label=None):
|
||||
GDataBase.__init__(self)
|
||||
self.startTime = startTime
|
||||
self.label = label
|
||||
|
||||
class Event(ContactsBase):
|
||||
"""The gContact:event element."""
|
||||
_tag = 'event'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['label'] = 'label'
|
||||
_attributes['rel'] = 'rel'
|
||||
_children['{%s}when' % GDataBase._namespace] = ('when', When)
|
||||
|
||||
def __init__(self, label=None, rel=None, when=None):
|
||||
ContactsBase.__init__(self)
|
||||
self.label = label
|
||||
self.rel = rel
|
||||
self.when = when
|
||||
|
||||
def EventFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(Event, xml_string)
|
||||
|
||||
class ExternalId(ContactsBase):
|
||||
"""The gContact:externalId element."""
|
||||
_tag = 'externalId'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['label'] = 'label'
|
||||
_attributes['rel'] = 'rel'
|
||||
_attributes['value'] = 'value'
|
||||
|
||||
def __init__(self, label=None, rel=None, value=None):
|
||||
ContactsBase.__init__(self)
|
||||
self.label = label
|
||||
self.rel = rel
|
||||
self.value = value
|
||||
|
||||
def ExternalIdFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(ExternalId, xml_string)
|
||||
|
||||
class Gender(ContactsBase):
|
||||
"""The gContact:gender element."""
|
||||
_tag = 'gender'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['value'] = 'value'
|
||||
|
||||
def __init__(self, value=None):
|
||||
ContactsBase.__init__(self)
|
||||
self.value = value
|
||||
|
||||
class Hobby(ContactsBase):
|
||||
"""The gContact:hobby element."""
|
||||
_tag = 'hobby'
|
||||
|
||||
class IM(GDataBase):
|
||||
"""The gd:im element."""
|
||||
_tag = 'im'
|
||||
_children = GDataBase._children.copy()
|
||||
_attributes = GDataBase._attributes.copy()
|
||||
_attributes['address'] = 'address'
|
||||
_attributes['primary'] = 'primary'
|
||||
_attributes['protocol'] = 'protocol'
|
||||
_attributes['label'] = 'label'
|
||||
_attributes['rel'] = 'rel'
|
||||
|
||||
def __init__(self, primary='false', rel=None, address=None, protocol=None, label=None):
|
||||
GDataBase.__init__(self)
|
||||
self.protocol = protocol
|
||||
self.address = address
|
||||
self.primary = primary
|
||||
self.rel = rel
|
||||
self.label = label
|
||||
|
||||
class Initials(ContactsBase):
|
||||
"""The gContact:initials element."""
|
||||
_tag = 'initials'
|
||||
|
||||
class Jot(ContactsBase):
|
||||
"""The gContact:jot element."""
|
||||
_tag = 'jot'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['rel'] = 'rel'
|
||||
|
||||
def __init__(self, rel=None, text=None):
|
||||
ContactsBase.__init__(self, text=text)
|
||||
self.rel = rel
|
||||
|
||||
class Language(ContactsBase):
|
||||
"""The gContact:language element."""
|
||||
_tag = 'language'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['code'] = 'code'
|
||||
_attributes['label'] = 'label'
|
||||
|
||||
def __init__(self, code=None, label=None):
|
||||
ContactsBase.__init__(self)
|
||||
self.code = code
|
||||
self.label = label
|
||||
|
||||
class MaidenName(ContactsBase):
|
||||
"""The gContact:maidenName element."""
|
||||
_tag = 'maidenName'
|
||||
|
||||
class Mileage(ContactsBase):
|
||||
"""The gContact:mileage element."""
|
||||
_tag = 'mileage'
|
||||
|
||||
class NamePrefix(GDataBase):
|
||||
"""The gd:namePrefix element."""
|
||||
_tag = 'namePrefix'
|
||||
|
||||
class GivenName(GDataBase):
|
||||
"""The gd:givenName element."""
|
||||
_tag = 'givenName'
|
||||
|
||||
class AdditionalName(GDataBase):
|
||||
"""The gd:additionalName element."""
|
||||
_tag = 'additionalName'
|
||||
|
||||
class FamilyName(GDataBase):
|
||||
"""The gd:familyName element."""
|
||||
_tag = 'familyName'
|
||||
|
||||
class NameSuffix(GDataBase):
|
||||
"""The gd:nameSuffix element."""
|
||||
_tag = 'nameSuffix'
|
||||
|
||||
class FullName(GDataBase):
|
||||
"""The gd:fullName element."""
|
||||
_tag = 'fullName'
|
||||
|
||||
class Name(GDataBase):
|
||||
"""The gd:name element."""
|
||||
_tag = 'name'
|
||||
_children = GDataBase._children.copy()
|
||||
_attributes = GDataBase._attributes.copy()
|
||||
_children['{%s}namePrefix' % GDataBase._namespace] = ('name_prefix', NamePrefix)
|
||||
_children['{%s}givenName' % GDataBase._namespace] = ('given_name', GivenName)
|
||||
_children['{%s}additionalName' % GDataBase._namespace] = ('additional_name', AdditionalName)
|
||||
_children['{%s}familyName' % GDataBase._namespace] = ('family_name', FamilyName)
|
||||
_children['{%s}nameSuffix' % GDataBase._namespace] = ('name_suffix', NameSuffix)
|
||||
_children['{%s}fullName' % GDataBase._namespace] = ('full_name', FullName)
|
||||
|
||||
def __init__(self, given_name=None, additional_name=None, family_name=None,
|
||||
name_prefix=None, name_suffix=None, full_name=None,):
|
||||
GDataBase.__init__(self)
|
||||
self.given_name = given_name
|
||||
self.additional_name = additional_name
|
||||
self.family_name = family_name
|
||||
self.name_prefix = name_prefix
|
||||
self.name_suffix = name_suffix
|
||||
self.full_name = full_name
|
||||
|
||||
class Nickname(ContactsBase):
|
||||
"""The gContact:nickname element."""
|
||||
_tag = 'nickname'
|
||||
|
||||
class Occupation(ContactsBase):
|
||||
"""The gContact:occupation element."""
|
||||
_tag = 'occupation'
|
||||
|
||||
class PhoneNumber(GDataBase):
|
||||
"""The gd:phoneNumber element."""
|
||||
_tag = 'phoneNumber'
|
||||
_children = GDataBase._children.copy()
|
||||
_attributes = GDataBase._attributes.copy()
|
||||
_attributes['label'] = 'label'
|
||||
_attributes['rel'] = 'rel'
|
||||
_attributes['uri'] = 'uri'
|
||||
_attributes['primary'] = 'primary'
|
||||
|
||||
def __init__(self, label=None, rel=None, uri=None, primary='false', text=None):
|
||||
GDataBase.__init__(self, text=text)
|
||||
self.label = label
|
||||
self.rel = rel
|
||||
self.uri = uri
|
||||
self.primary = primary
|
||||
|
||||
class OrgName(GDataBase):
|
||||
"""The gd:orgName element."""
|
||||
_tag = 'orgName'
|
||||
|
||||
class OrgTitle(GDataBase):
|
||||
"""The gd:orgTitle element."""
|
||||
_tag = 'orgTitle'
|
||||
|
||||
class OrgSymbol(GDataBase):
|
||||
"""The gd:orgSymbol element."""
|
||||
_tag = 'orgSymbol'
|
||||
|
||||
class OrgDepartment(GDataBase):
|
||||
"""The gd:orgDepartment element."""
|
||||
_tag = 'orgDepartment'
|
||||
|
||||
class OrgJobDescription(GDataBase):
|
||||
"""The gd:orgJobDescription element."""
|
||||
_tag = 'orgJobDescription'
|
||||
|
||||
class Where(GDataBase):
|
||||
"""The gd:where element."""
|
||||
_tag = 'where'
|
||||
_children = GDataBase._children.copy()
|
||||
_attributes = GDataBase._attributes.copy()
|
||||
_attributes['valueString'] = 'value_string'
|
||||
|
||||
def __init__(self, value_string=None):
|
||||
GDataBase.__init__(self)
|
||||
self.value_string = value_string
|
||||
|
||||
class Organization(GDataBase):
|
||||
"""The gd:organization element."""
|
||||
_tag = 'organization'
|
||||
_children = GDataBase._children.copy()
|
||||
_attributes = GDataBase._attributes.copy()
|
||||
_attributes['label'] = 'label'
|
||||
_attributes['rel'] = 'rel'
|
||||
_attributes['primary'] = 'primary'
|
||||
_children['{%s}orgName' % GDataBase._namespace] = ('name', OrgName)
|
||||
_children['{%s}orgSymbol' % GDataBase._namespace] = ('symbol', OrgSymbol)
|
||||
_children['{%s}orgTitle' % GDataBase._namespace] = ('title', OrgTitle)
|
||||
_children['{%s}orgDepartment' % GDataBase._namespace] = ('department', OrgDepartment)
|
||||
_children['{%s}orgJobDescription' % GDataBase._namespace] = ('job_description', OrgJobDescription)
|
||||
_children['{%s}where' % GDataBase._namespace] = ('where', Where)
|
||||
|
||||
def __init__(self, label=None, rel=None, primary='false', name=None,
|
||||
title=None, symbol=None, department=None, job_description=None, where=None,):
|
||||
GDataBase.__init__(self)
|
||||
self.label = label
|
||||
self.rel = rel
|
||||
self.primary = primary
|
||||
self.name = name
|
||||
self.symbol = symbol
|
||||
self.title = title
|
||||
self.department = department
|
||||
self.job_description = job_description
|
||||
self.where = where
|
||||
|
||||
class PostalAddress(GDataBase):
|
||||
"""The gd:postalAddress element."""
|
||||
_tag = 'postalAddress'
|
||||
_children = GDataBase._children.copy()
|
||||
_attributes = GDataBase._attributes.copy()
|
||||
_attributes['label'] = 'label'
|
||||
_attributes['rel'] = 'rel'
|
||||
_attributes['primary'] = 'primary'
|
||||
|
||||
def __init__(self, primary=None, rel=None, label=None, text=None):
|
||||
GDataBase.__init__(self, text=text)
|
||||
self.label = label
|
||||
self.rel = rel
|
||||
self.primary = primary
|
||||
|
||||
class Priority(ContactsBase):
|
||||
"""The gContact:priority element."""
|
||||
_tag = 'priority'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['rel'] = 'rel'
|
||||
|
||||
def __init__(self, rel=None):
|
||||
ContactsBase.__init__(self)
|
||||
self.rel = rel
|
||||
|
||||
class Relation(ContactsBase):
|
||||
"""The gContact:relation element."""
|
||||
_tag = 'relation'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['label'] = 'label'
|
||||
_attributes['rel'] = 'rel'
|
||||
|
||||
def __init__(self, label=None, rel=None, text=None):
|
||||
ContactsBase.__init__(self, text=text)
|
||||
self.label = label
|
||||
self.rel = rel
|
||||
|
||||
def RelationFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(Relation, xml_string)
|
||||
|
||||
class Sensitivity(ContactsBase):
|
||||
"""The gContact:sensitivity element."""
|
||||
_tag = 'sensitivity'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['rel'] = 'rel'
|
||||
|
||||
def __init__(self, rel=None):
|
||||
ContactsBase.__init__(self)
|
||||
self.rel = rel
|
||||
|
||||
class ShortName(ContactsBase):
|
||||
"""The gContact:shortName element."""
|
||||
_tag = 'shortName'
|
||||
|
||||
class Street(GDataBase):
|
||||
"""The gd:street element.
|
||||
Can be street, avenue, road, etc. This element also includes the
|
||||
house number and room/apartment/flat/floor number.
|
||||
"""
|
||||
_tag = 'street'
|
||||
|
||||
class PoBox(GDataBase):
|
||||
"""The gd:pobox element.
|
||||
Covers actual P.O. boxes, drawers, locked bags, etc. This is usually
|
||||
but not always mutually exclusive with street.
|
||||
"""
|
||||
_tag = 'pobox'
|
||||
|
||||
class Neighborhood(GDataBase):
|
||||
"""The gd:neighborhood element.
|
||||
This is used to disambiguate a street address when a city contains more
|
||||
than one street with the same name, or to specify a small place whose
|
||||
mail is routed through a larger postal town. In China it could be a
|
||||
county or a minor city.
|
||||
"""
|
||||
_tag = 'neighborhood'
|
||||
|
||||
class City(GDataBase):
|
||||
"""The gd:city element.
|
||||
Can be city, village, town, borough, etc. This is the postal town and
|
||||
not necessarily the place of residence or place of business.
|
||||
"""
|
||||
_tag = 'city'
|
||||
|
||||
class Region(GDataBase):
|
||||
"""The gd:region element.
|
||||
A state, province, county (in Ireland), Land (in Germany),
|
||||
departement (in France), etc.
|
||||
"""
|
||||
_tag = 'region'
|
||||
|
||||
class Postcode(GDataBase):
|
||||
"""The gd:postcode element.
|
||||
Postal code. Usually country-wide, but sometimes specific to the
|
||||
city (e.g. "2" in "Dublin 2, Ireland" addresses).
|
||||
"""
|
||||
_tag = 'postcode'
|
||||
|
||||
class Country(GDataBase):
|
||||
"""The gd:country element.
|
||||
The name or code of the country.
|
||||
"""
|
||||
_tag = 'country'
|
||||
|
||||
class FormattedAddress(GDataBase):
|
||||
"""The gd:formattedAddress element."""
|
||||
_tag = 'formattedAddress'
|
||||
|
||||
class StructuredPostalAddress(GDataBase):
|
||||
"""The gd:structuredPostalAddress element."""
|
||||
_tag = 'structuredPostalAddress'
|
||||
_children = GDataBase._children.copy()
|
||||
_attributes = GDataBase._attributes.copy()
|
||||
_attributes['label'] = 'label'
|
||||
_attributes['rel'] = 'rel'
|
||||
_attributes['primary'] = 'primary'
|
||||
_children['{%s}street' % GDataBase._namespace] = ('street', Street)
|
||||
_children['{%s}pobox' % GDataBase._namespace] = ('pobox', PoBox)
|
||||
_children['{%s}neighborhood' % GDataBase._namespace] = ('neighborhood', Neighborhood)
|
||||
_children['{%s}city' % GDataBase._namespace] = ('city', City)
|
||||
_children['{%s}region' % GDataBase._namespace] = ('region', Region)
|
||||
_children['{%s}postcode' % GDataBase._namespace] = ('postcode', Postcode)
|
||||
_children['{%s}country' % GDataBase._namespace] = ('country', Country)
|
||||
_children['{%s}formattedAddress' % GDataBase._namespace] = ('formatted_address', FormattedAddress)
|
||||
|
||||
def __init__(self, rel=None, label=None, primary='false',
|
||||
street=None,
|
||||
pobox=None,
|
||||
neighborhood=None,
|
||||
city=None,
|
||||
region=None,
|
||||
postcode=None,
|
||||
country=None,
|
||||
formatted_address=None):
|
||||
GDataBase.__init__(self)
|
||||
self.label = label
|
||||
self.rel = rel
|
||||
self.primary = primary
|
||||
self.street = street
|
||||
self.pobox = pobox
|
||||
self.neighborhood = neighborhood
|
||||
self.city = city
|
||||
self.region = region
|
||||
self.postcode = postcode
|
||||
self.country = country
|
||||
self.formatted_address = formatted_address
|
||||
|
||||
class Subject(ContactsBase):
|
||||
"""The gContact:Subject element."""
|
||||
_tag = 'subject'
|
||||
|
||||
class UserDefinedField(ContactsBase):
|
||||
"""The gContact:userDefinedField element."""
|
||||
_tag = 'userDefinedField'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['key'] = 'key'
|
||||
_attributes['value'] = 'value'
|
||||
|
||||
def __init__(self, key=None, value=None):
|
||||
ContactsBase.__init__(self)
|
||||
self.key = key
|
||||
self.value = value
|
||||
|
||||
def UserDefinedFieldFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(UserDefinedField, xml_string)
|
||||
|
||||
class Website(ContactsBase):
|
||||
"""The gContact:Website element."""
|
||||
_tag = 'website'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['href'] = 'href'
|
||||
_attributes['label'] = 'label'
|
||||
_attributes['primary'] = 'primary'
|
||||
_attributes['rel'] = 'rel'
|
||||
|
||||
def __init__(self, href=None, label=None, primary='false', rel=None):
|
||||
ContactsBase.__init__(self)
|
||||
self.href = href
|
||||
self.label = label
|
||||
self.primary = primary
|
||||
self.rel = rel
|
||||
|
||||
def WebsiteFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(Website, xml_string)
|
||||
|
||||
class PersonEntry(gdata.BatchEntry):
|
||||
"""Base class for ContactEntry and ProfileEntry."""
|
||||
_children = gdata.BatchEntry._children.copy()
|
||||
_children['{%s}billingInformation' % CONTACTS_NAMESPACE] = ('billingInformation', BillingInformation)
|
||||
_children['{%s}birthday' % CONTACTS_NAMESPACE] = ('birthday', Birthday)
|
||||
_children['{%s}calendarLink' % CONTACTS_NAMESPACE] = ('calendarLink', [CalendarLink])
|
||||
_children['{%s}content' % atom.ATOM_NAMESPACE] = ('content', Content)
|
||||
_children['{%s}directoryServer' % CONTACTS_NAMESPACE] = ('directoryServer', DirectoryServer)
|
||||
_children['{%s}email' % gdata.GDATA_NAMESPACE] = ('email', [Email])
|
||||
_children['{%s}event' % CONTACTS_NAMESPACE] = ('event', [Event])
|
||||
_children['{%s}externalId' % CONTACTS_NAMESPACE] = ('externalId', [ExternalId])
|
||||
_children['{%s}gender' % CONTACTS_NAMESPACE] = ('gender', Gender)
|
||||
_children['{%s}hobby' % CONTACTS_NAMESPACE] = ('hobby', [Hobby])
|
||||
_children['{%s}im' % gdata.GDATA_NAMESPACE] = ('im', [IM])
|
||||
_children['{%s}initials' % CONTACTS_NAMESPACE] = ('initials', Initials)
|
||||
_children['{%s}jot' % CONTACTS_NAMESPACE] = ('jot', [Jot])
|
||||
_children['{%s}language' % CONTACTS_NAMESPACE] = ('language', Language)
|
||||
_children['{%s}maidenName' % CONTACTS_NAMESPACE] = ('maidenName', MaidenName)
|
||||
_children['{%s}mileage' % CONTACTS_NAMESPACE] = ('mileage', Mileage)
|
||||
_children['{%s}name' % gdata.GDATA_NAMESPACE] = ('name', Name)
|
||||
_children['{%s}nickname' % CONTACTS_NAMESPACE] = ('nickname', Nickname)
|
||||
_children['{%s}occupation' % CONTACTS_NAMESPACE] = ('occupation', Occupation)
|
||||
_children['{%s}organization' % gdata.GDATA_NAMESPACE] = ('organization', [Organization])
|
||||
_children['{%s}phoneNumber' % gdata.GDATA_NAMESPACE] = ('phoneNumber', [PhoneNumber])
|
||||
_children['{%s}postalAddress' % gdata.GDATA_NAMESPACE] = ('postalAddress', [PostalAddress])
|
||||
_children['{%s}priority' % CONTACTS_NAMESPACE] = ('priority', Priority)
|
||||
_children['{%s}relation' % CONTACTS_NAMESPACE] = ('relation', [Relation])
|
||||
_children['{%s}sensitivity' % CONTACTS_NAMESPACE] = ('sensitivity', Sensitivity)
|
||||
_children['{%s}shortName' % CONTACTS_NAMESPACE] = ('shortName', ShortName)
|
||||
_children['{%s}structuredPostalAddress' % gdata.GDATA_NAMESPACE] = ('structuredPostalAddress', [StructuredPostalAddress])
|
||||
_children['{%s}subject' % CONTACTS_NAMESPACE] = ('subject', Subject)
|
||||
_children['{%s}userDefinedField' % CONTACTS_NAMESPACE] = ('userDefinedField', [UserDefinedField])
|
||||
_children['{%s}website' % CONTACTS_NAMESPACE] = ('website', [Website])
|
||||
_children['{%s}where' % gdata.GDATA_NAMESPACE] = ('where', Where)
|
||||
|
||||
_attributes = gdata.BatchEntry._attributes.copy()
|
||||
_attributes['{%s}etag' % gdata.GDATA_NAMESPACE] = 'etag'
|
||||
|
||||
def __init__(self,
|
||||
billingInformation=None,
|
||||
birthday=None,
|
||||
calendarLink=None,
|
||||
content=None,
|
||||
directoryServer=None,
|
||||
email=None,
|
||||
event=None,
|
||||
externalId=None,
|
||||
gender=None,
|
||||
hobby=None,
|
||||
im=None,
|
||||
initials=None,
|
||||
jot=None,
|
||||
language=None,
|
||||
maidenName=None,
|
||||
mileage=None,
|
||||
name=None,
|
||||
nickname=None,
|
||||
occupation=None,
|
||||
organization=None,
|
||||
phoneNumber=None,
|
||||
postalAddress=None,
|
||||
priority=None,
|
||||
relation=None,
|
||||
sensitivity=None,
|
||||
shortName=None,
|
||||
structuredPostalAddress=None,
|
||||
subject=None,
|
||||
text=None,
|
||||
title=None,
|
||||
userDefinedField=None,
|
||||
website=None,
|
||||
where=None,
|
||||
etag=None):
|
||||
gdata.BatchEntry.__init__(self)
|
||||
self.billingInformation = billingInformation
|
||||
self.birthday = birthday
|
||||
self.calendarLink = calendarLink or []
|
||||
self.content = content
|
||||
self.directoryServer = directoryServer
|
||||
self.email = email or []
|
||||
self.event = event or []
|
||||
self.externalId = externalId or []
|
||||
self.gender = gender
|
||||
self.hobby = hobby or []
|
||||
self.im = im or []
|
||||
self.initials = initials
|
||||
self.jot = jot or []
|
||||
self.language = language
|
||||
self.maidenName = maidenName
|
||||
self.mileage = mileage
|
||||
self.name = name
|
||||
self.nickname = nickname
|
||||
self.occupation = occupation
|
||||
self.organization = organization or []
|
||||
self.phoneNumber = phoneNumber or []
|
||||
self.postalAddress = postalAddress or []
|
||||
self.priority = priority
|
||||
self.relation = relation or []
|
||||
self.sensitivity = sensitivity
|
||||
self.shortName = shortName
|
||||
self.structuredPostalAddress = structuredPostalAddress or []
|
||||
self.subject = subject
|
||||
self.text = text
|
||||
self.userDefinedField = userDefinedField or []
|
||||
self.website = website or []
|
||||
self.where = where
|
||||
self.extension_attributes = {}
|
||||
self.extension_elements = []
|
||||
self.etag = etag
|
||||
|
||||
class Deleted(GDataBase):
|
||||
"""The gd:Deleted element."""
|
||||
_tag = 'deleted'
|
||||
|
||||
class GroupMembershipInfo(ContactsBase):
|
||||
"""The Google Contacts GroupMembershipInfo element."""
|
||||
_tag = 'groupMembershipInfo'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['deleted'] = 'deleted'
|
||||
_attributes['href'] = 'href'
|
||||
|
||||
def __init__(self, deleted=None, href=None, text=None):
|
||||
ContactsBase.__init__(self, text=text)
|
||||
self.deleted = deleted
|
||||
self.href = href
|
||||
|
||||
class ContactEntry(PersonEntry):
|
||||
"""Represents a contact."""
|
||||
_tag = 'entry'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = PersonEntry._children.copy()
|
||||
|
||||
_children['{%s}deleted' % gdata.GDATA_NAMESPACE] = ('deleted', Deleted)
|
||||
_children['{%s}groupMembershipInfo' % CONTACTS_NAMESPACE] = ('groupMembershipInfo', [GroupMembershipInfo])
|
||||
_children['{%s}extendedProperty' % gdata.GDATA_NAMESPACE] = ('extended_property', [gdata.ExtendedProperty])
|
||||
# Overwrite the organization rule in PersonEntry so that a ContactEntry
|
||||
# may only contain one <gd:organization> element.
|
||||
#_children['{%s}organization' % gdata.GDATA_NAMESPACE] = ('organization', Organization)
|
||||
|
||||
def __init__(self,
|
||||
billingInformation=None,
|
||||
birthday=None,
|
||||
calendarLink=None,
|
||||
content=None,
|
||||
deleted=None,
|
||||
directoryServer=None,
|
||||
email=None,
|
||||
event=None,
|
||||
extended_property=None,
|
||||
externalId=None,
|
||||
gender=None,
|
||||
groupMembershipInfo=None,
|
||||
hobby=None,
|
||||
im=None,
|
||||
initials=None,
|
||||
jot=None,
|
||||
language=None,
|
||||
maidenName=None,
|
||||
mileage=None,
|
||||
name=None,
|
||||
nickname=None,
|
||||
occupation=None,
|
||||
organization=None,
|
||||
phoneNumber=None,
|
||||
postalAddress=None,
|
||||
priority=None,
|
||||
relation=None,
|
||||
sensitivity=None,
|
||||
shortName=None,
|
||||
structuredPostalAddress=None,
|
||||
subject=None,
|
||||
text=None,
|
||||
title=None,
|
||||
userDefinedField=None,
|
||||
website=None,
|
||||
where=None,
|
||||
etag=None):
|
||||
PersonEntry.__init__(self,
|
||||
billingInformation=billingInformation,
|
||||
birthday=birthday,
|
||||
calendarLink=calendarLink,
|
||||
content=content,
|
||||
directoryServer=directoryServer,
|
||||
email=email,
|
||||
event=event,
|
||||
externalId=externalId,
|
||||
gender=gender,
|
||||
hobby=hobby,
|
||||
im=im,
|
||||
initials=initials,
|
||||
jot=jot,
|
||||
language=language,
|
||||
maidenName=maidenName,
|
||||
mileage=mileage,
|
||||
name=name,
|
||||
nickname=nickname,
|
||||
occupation=occupation,
|
||||
organization=organization,
|
||||
phoneNumber=phoneNumber,
|
||||
postalAddress=postalAddress,
|
||||
priority=priority,
|
||||
relation=relation,
|
||||
sensitivity=sensitivity,
|
||||
shortName=shortName,
|
||||
structuredPostalAddress=structuredPostalAddress,
|
||||
subject=subject,
|
||||
text=text,
|
||||
title=title,
|
||||
userDefinedField=userDefinedField,
|
||||
website=website,
|
||||
where=where,
|
||||
etag=etag)
|
||||
self.deleted = deleted
|
||||
self.extended_property = extended_property or []
|
||||
self.groupMembershipInfo = groupMembershipInfo or []
|
||||
|
||||
def GetPhotoLink(self):
|
||||
for a_link in self.link:
|
||||
if a_link.rel == PHOTO_LINK_REL:
|
||||
return a_link
|
||||
return None
|
||||
|
||||
def GetPhotoEditLink(self):
|
||||
for a_link in self.link:
|
||||
if a_link.rel == PHOTO_EDIT_LINK_REL:
|
||||
return a_link
|
||||
return None
|
||||
|
||||
def ContactEntryFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(ContactEntry, xml_string)
|
||||
|
||||
class ContactsFeed(gdata.BatchFeed, gdata.LinkFinder):
|
||||
"""A Google contacts feed flavor of an Atom Feed."""
|
||||
_tag = 'feed'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.BatchFeed._children.copy()
|
||||
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [ContactEntry])
|
||||
|
||||
def __init__(self):
|
||||
gdata.BatchFeed.__init__(self)
|
||||
|
||||
def ContactsFeedFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(ContactsFeed, xml_string)
|
||||
|
||||
class GroupEntry(gdata.BatchEntry):
|
||||
"""Represents a contact group."""
|
||||
_children = gdata.BatchEntry._children.copy()
|
||||
_children['{%s}deleted' % gdata.GDATA_NAMESPACE] = ('deleted', Deleted)
|
||||
_children['{%s}extendedProperty' % gdata.GDATA_NAMESPACE] = ('extended_property', [gdata.ExtendedProperty])
|
||||
|
||||
_attributes = gdata.BatchEntry._attributes.copy()
|
||||
_attributes['{%s}etag' % gdata.GDATA_NAMESPACE] = 'etag'
|
||||
|
||||
def __init__(self,
|
||||
title=None,
|
||||
deleted=None,
|
||||
extended_property=None,
|
||||
etag=None):
|
||||
gdata.BatchEntry.__init__(self)
|
||||
self.title = title
|
||||
self.deleted = deleted
|
||||
self.extended_property = extended_property or []
|
||||
self.etag = etag
|
||||
|
||||
def GroupEntryFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(GroupEntry, xml_string)
|
||||
|
||||
class GroupsFeed(gdata.BatchFeed):
|
||||
"""A Google contact groups feed flavor of an Atom Feed."""
|
||||
_tag = 'feed'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.BatchFeed._children.copy()
|
||||
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [GroupEntry])
|
||||
|
||||
def __init__(self):
|
||||
gdata.BatchFeed.__init__(self)
|
||||
|
||||
def GroupsFeedFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(GroupsFeed, xml_string)
|
||||
@@ -1,355 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2009 Google Inc. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""ContactsService extends the GDataService for Google Contacts operations.
|
||||
|
||||
ContactsService: Provides methods to query feeds and manipulate items.
|
||||
Extends GDataService.
|
||||
"""
|
||||
|
||||
|
||||
import gdata.apps
|
||||
import gdata.apps.service
|
||||
import gdata.service
|
||||
|
||||
|
||||
class ContactsService(gdata.service.GDataService):
|
||||
"""Client for the Google Contacts service."""
|
||||
|
||||
def __init__(self, email=None, password=None, source=None,
|
||||
server='www.google.com', additional_headers=None,
|
||||
contact_list='default', contactFeed=True, **kwargs):
|
||||
"""Creates a client for the Contacts service.
|
||||
|
||||
Args:
|
||||
email: string (optional) The user's email address, used for
|
||||
authentication.
|
||||
password: string (optional) The user's password.
|
||||
source: string (optional) The name of the user's application.
|
||||
server: string (optional) The name of the server to which a connection
|
||||
will be opened. Default value: 'www.google.com'.
|
||||
contact_list: string (optional) The name of the default contact list to
|
||||
use when no URI is specified to the methods of the service.
|
||||
Default value: 'default' (the logged in user's contact list).
|
||||
contactFeed: Boolean (optional) Is this contacts feed or a gal feed
|
||||
Default value: True (the logged in user's contact list).
|
||||
**kwargs: The other parameters to pass to gdata.service.GDataService
|
||||
constructor.
|
||||
"""
|
||||
|
||||
self.contact_list = contact_list
|
||||
self.feed_type = ['gal', 'contacts'][contactFeed]
|
||||
if additional_headers == None:
|
||||
additional_headers = {}
|
||||
additional_headers['GData-Version'] = ['1.1', '3.1'][contactFeed]
|
||||
gdata.service.GDataService.__init__(self,
|
||||
email=email, password=password, service='cp', source=source,
|
||||
server=server, additional_headers=additional_headers, **kwargs)
|
||||
self.ssl = True
|
||||
self.port = 443
|
||||
|
||||
def _CleanUri(self, uri):
|
||||
"""Sanitizes a feed URI.
|
||||
|
||||
Args:
|
||||
uri: The URI to sanitize, can be relative or absolute.
|
||||
|
||||
Returns:
|
||||
The given URI without its https://server prefix, if any.
|
||||
Keeps the leading slash of the URI.
|
||||
"""
|
||||
url_prefix = 'https://%s' % self.server
|
||||
if uri.startswith(url_prefix):
|
||||
uri = uri[len(url_prefix):]
|
||||
return uri
|
||||
|
||||
def GetContactFeedUri(self, contact_list=None, projection='full', contactId=None):
|
||||
"""Builds a contact feed URI. """
|
||||
contact_list = contact_list or self.contact_list
|
||||
uri = 'https://{0}/m8/feeds/{1}/{2}/{3}'.format(self.server, self.feed_type, contact_list, projection)
|
||||
if contactId:
|
||||
uri += '/{0}'.format(contactId)
|
||||
return uri
|
||||
|
||||
def GetContactsFeed(self, uri=None,
|
||||
extra_headers=None, url_params=None, escape_params=True):
|
||||
uri = uri or self.GetContactFeedUri()
|
||||
try:
|
||||
return self.Get(uri,
|
||||
url_params=url_params, extra_headers=extra_headers, escape_params=escape_params,
|
||||
converter=gdata.apps.contacts.ContactsFeedFromString)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def GetContact(self, uri):
|
||||
try:
|
||||
return self.Get(uri, converter=gdata.apps.contacts.ContactEntryFromString)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def CreateContact(self, new_contact, insert_uri=None, url_params=None,
|
||||
escape_params=True):
|
||||
"""Adds an new contact to Google Contacts.
|
||||
|
||||
Args:
|
||||
new_contact: atom.Entry or subclass A new contact which is to be added to
|
||||
Google Contacts.
|
||||
insert_uri: the URL to post new contacts to the feed
|
||||
url_params: dict (optional) Additional URL parameters to be included
|
||||
in the insertion request.
|
||||
escape_params: boolean (optional) If true, the url_parameters will be
|
||||
escaped before they are included in the request.
|
||||
|
||||
Returns:
|
||||
On successful insert, an entry containing the contact created
|
||||
On failure, a RequestError is raised of the form:
|
||||
{'status': HTTP status code from server,
|
||||
'reason': HTTP reason from the server,
|
||||
'body': HTTP body of the server's response}
|
||||
"""
|
||||
insert_uri = insert_uri or self.GetContactFeedUri()
|
||||
try:
|
||||
return self.Post(new_contact, insert_uri, url_params=url_params,
|
||||
escape_params=escape_params,
|
||||
converter=gdata.apps.contacts.ContactEntryFromString)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def UpdateContact(self, edit_uri, updated_contact, extra_headers=None, url_params=None,
|
||||
escape_params=True):
|
||||
"""Updates an existing contact.
|
||||
|
||||
Args:
|
||||
edit_uri: string The edit link URI for the element being updated
|
||||
updated_contact: string, atom.Entry or subclass containing
|
||||
the Atom Entry which will replace the contact which is
|
||||
stored at the edit_url
|
||||
url_params: dict (optional) Additional URL parameters to be included
|
||||
in the update request.
|
||||
escape_params: boolean (optional) If true, the url_parameters will be
|
||||
escaped before they are included in the request.
|
||||
|
||||
Returns:
|
||||
On successful update, a httplib.HTTPResponse containing the server's
|
||||
response to the PUT request.
|
||||
On failure, a RequestError is raised of the form:
|
||||
{'status': HTTP status code from server,
|
||||
'reason': HTTP reason from the server,
|
||||
'body': HTTP body of the server's response}
|
||||
"""
|
||||
try:
|
||||
return self.Put(updated_contact, self._CleanUri(edit_uri),
|
||||
url_params=url_params, extra_headers=extra_headers,
|
||||
escape_params=escape_params,
|
||||
converter=gdata.apps.contacts.ContactEntryFromString)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def DeleteContact(self, edit_uri, extra_headers=None,
|
||||
url_params=None, escape_params=True):
|
||||
"""Removes an contact with the specified ID from Google Contacts.
|
||||
|
||||
Args:
|
||||
edit_uri: string The edit URL of the entry to be deleted. Example:
|
||||
'/m8/feeds/contacts/default/full/xxx/yyy'
|
||||
extra_headers: dict (optional)
|
||||
url_params: dict (optional) Additional URL parameters to be included
|
||||
in the deletion request.
|
||||
escape_params: boolean (optional) If true, the url_parameters will be
|
||||
escaped before they are included in the request.
|
||||
|
||||
Returns:
|
||||
On successful delete, a httplib.HTTPResponse containing the server's
|
||||
response to the DELETE request.
|
||||
On failure, a RequestError is raised of the form:
|
||||
{'status': HTTP status code from server,
|
||||
'reason': HTTP reason from the server,
|
||||
'body': HTTP body of the server's response}
|
||||
"""
|
||||
try:
|
||||
return self.Delete(self._CleanUri(edit_uri),
|
||||
url_params=url_params, escape_params=escape_params, extra_headers=extra_headers)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def ChangePhoto(self, media, contact_entry_or_url, content_type=None,
|
||||
content_length=None, extra_headers=None):
|
||||
"""Change the photo for the contact by uploading a new photo.
|
||||
|
||||
Performs a PUT against the photo edit URL to send the binary data for the
|
||||
photo.
|
||||
|
||||
Args:
|
||||
media: filename, file-like-object, or a gdata.MediaSource object to send.
|
||||
contact_entry_or_url: ContactEntry or str If it is a ContactEntry, this
|
||||
method will search for an edit photo link URL and
|
||||
perform a PUT to the URL.
|
||||
content_type: str (optional) the mime type for the photo data. This is
|
||||
necessary if media is a file or file name, but if media
|
||||
is a MediaSource object then the media object can contain
|
||||
the mime type. If media_type is set, it will override the
|
||||
mime type in the media object.
|
||||
content_length: int or str (optional) Specifying the content length is
|
||||
only required if media is a file-like object. If media
|
||||
is a filename, the length is determined using
|
||||
os.path.getsize. If media is a MediaSource object, it is
|
||||
assumed that it already contains the content length.
|
||||
extra_headers: dict (optional)
|
||||
"""
|
||||
if isinstance(contact_entry_or_url, gdata.apps.contacts.ContactEntry):
|
||||
# url = contact_entry_or_url.GetPhotoEditLink().href
|
||||
url = contact_entry_or_url.GetPhotoLink().href
|
||||
else:
|
||||
url = contact_entry_or_url
|
||||
if isinstance(media, gdata.MediaSource):
|
||||
payload = media
|
||||
# If the media object is a file-like object, then use it as the file
|
||||
# handle in the in the MediaSource.
|
||||
elif hasattr(media, 'read'):
|
||||
payload = gdata.MediaSource(file_handle=media,
|
||||
content_type=content_type, content_length=content_length)
|
||||
# Assume that the media object is a file name.
|
||||
else:
|
||||
payload = gdata.MediaSource(content_type=content_type,
|
||||
content_length=content_length, file_path=media)
|
||||
return self.Put(payload, url, extra_headers=extra_headers)
|
||||
|
||||
def GetPhoto(self, contact_entry_or_url):
|
||||
"""Retrives the binary data for the contact's profile photo as a string.
|
||||
|
||||
Args:
|
||||
contact_entry_or_url: a gdata.apps.contacts.ContactEntry object or a string
|
||||
containing the photo link's URL. If the contact entry does not
|
||||
contain a photo link, the image will not be fetched and this method
|
||||
will return None.
|
||||
"""
|
||||
# TODO: add the ability to write out the binary image data to a file,
|
||||
# reading and writing a chunk at a time to avoid potentially using up
|
||||
# large amounts of memory.
|
||||
url = None
|
||||
if isinstance(contact_entry_or_url, gdata.apps.contacts.ContactEntry):
|
||||
photo_link = contact_entry_or_url.GetPhotoLink()
|
||||
if photo_link:
|
||||
url = photo_link.href
|
||||
else:
|
||||
url = contact_entry_or_url
|
||||
if url:
|
||||
try:
|
||||
return self.Get(url, converter=str)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
else:
|
||||
return None
|
||||
|
||||
def DeletePhoto(self, contact_entry_or_url, extra_headers=None):
|
||||
"""Deletes the contact's profile photo.
|
||||
|
||||
Args:
|
||||
contact_entry_or_url: a gdata.apps.contacts.ContactEntry object or a string
|
||||
containing the photo link's URL.
|
||||
will return None.
|
||||
extra_headers: dict (optional)
|
||||
"""
|
||||
url = None
|
||||
if isinstance(contact_entry_or_url, gdata.apps.contacts.ContactEntry):
|
||||
# url = contact_entry_or_url.GetPhotoEditLink().href
|
||||
url = contact_entry_or_url.GetPhotoLink().href
|
||||
else:
|
||||
url = contact_entry_or_url
|
||||
if url:
|
||||
self.Delete(url, extra_headers=extra_headers)
|
||||
|
||||
def ExecuteBatch(self, batch_feed, url,
|
||||
converter=gdata.apps.contacts.ContactsFeedFromString):
|
||||
"""Sends a batch request feed to the server.
|
||||
|
||||
Args:
|
||||
batch_feed: gdata.apps.contacts.ContactFeed A feed containing batch
|
||||
request entries. Each entry contains the operation to be performed
|
||||
on the data contained in the entry. For example an entry with an
|
||||
operation type of insert will be used as if the individual entry
|
||||
had been inserted.
|
||||
url: str The batch URL to which these operations should be applied.
|
||||
converter: Function (optional) The function used to convert the server's
|
||||
response to an object. The default value is ContactsFeedFromString.
|
||||
|
||||
Returns:
|
||||
The results of the batch request's execution on the server. If the
|
||||
default converter is used, this is stored in a ContactsFeed.
|
||||
"""
|
||||
return self.Post(batch_feed, url, converter=converter)
|
||||
|
||||
def GetContactGroupFeedUri(self, contact_list=None, projection='full', groupId=None):
|
||||
"""Builds a contact feed URI. """
|
||||
contact_list = contact_list or self.contact_list
|
||||
uri = 'https://{0}/m8/feeds/groups/{1}/{2}'.format(self.server, contact_list, projection)
|
||||
if groupId:
|
||||
uri += '/{0}'.format(groupId)
|
||||
return uri
|
||||
|
||||
def GetGroupsFeed(self, uri=None,
|
||||
extra_headers=None, url_params=None, escape_params=True):
|
||||
uri = uri or self.GetContactGroupFeedUri()
|
||||
try:
|
||||
return self.Get(uri,
|
||||
url_params=url_params, extra_headers=extra_headers, escape_params=escape_params,
|
||||
converter=gdata.apps.contacts.GroupsFeedFromString)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def GetGroup(self, uri):
|
||||
try:
|
||||
return self.Get(uri, converter=gdata.apps.contacts.GroupEntryFromString)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def CreateGroup(self, new_group, insert_uri=None, url_params=None,
|
||||
escape_params=True):
|
||||
insert_uri = insert_uri or self.GetContactGroupFeedUri()
|
||||
try:
|
||||
return self.Post(new_group, insert_uri, url_params=url_params,
|
||||
escape_params=escape_params,
|
||||
converter=gdata.apps.contacts.GroupEntryFromString)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def UpdateGroup(self, edit_uri, updated_group, extra_headers=None, url_params=None,
|
||||
escape_params=True):
|
||||
try:
|
||||
return self.Put(updated_group, self._CleanUri(edit_uri),
|
||||
url_params=url_params, extra_headers=extra_headers,
|
||||
escape_params=escape_params,
|
||||
converter=gdata.apps.contacts.GroupEntryFromString)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def DeleteGroup(self, edit_uri, extra_headers=None,
|
||||
url_params=None, escape_params=True):
|
||||
try:
|
||||
return self.Delete(self._CleanUri(edit_uri),
|
||||
url_params=url_params, escape_params=escape_params, extra_headers=extra_headers)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
class ContactsQuery(gdata.service.Query):
|
||||
|
||||
def __init__(self, feed=None, text_query=None, params=None,
|
||||
categories=None, group=None):
|
||||
self.feed = feed or '/m8/feeds/contacts/default/full'
|
||||
if group:
|
||||
self['group'] = group
|
||||
gdata.service.Query.__init__(self, feed=self.feed, text_query=text_query,
|
||||
params=params, categories=categories)
|
||||
@@ -1,544 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright (C) 2007 SIOS Technology, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
__author__ = 'tmatsuo@sios.com (Takashi MATSUO)'
|
||||
|
||||
import lxml.etree as ElementTree
|
||||
import urllib.request, urllib.parse, urllib.error
|
||||
import gdata
|
||||
import atom.service
|
||||
import gdata.service
|
||||
import gdata.apps
|
||||
import atom
|
||||
|
||||
API_VER="2.0"
|
||||
HTTP_OK=200
|
||||
|
||||
UNKOWN_ERROR=1000
|
||||
USER_DELETED_RECENTLY=1100
|
||||
USER_SUSPENDED=1101
|
||||
DOMAIN_USER_LIMIT_EXCEEDED=1200
|
||||
DOMAIN_ALIAS_LIMIT_EXCEEDED=1201
|
||||
DOMAIN_SUSPENDED=1202
|
||||
DOMAIN_FEATURE_UNAVAILABLE=1203
|
||||
ENTITY_EXISTS=1300
|
||||
ENTITY_DOES_NOT_EXIST=1301
|
||||
ENTITY_NAME_IS_RESERVED=1302
|
||||
ENTITY_NAME_NOT_VALID=1303
|
||||
INVALID_GIVEN_NAME=1400
|
||||
INVALID_FAMILY_NAME=1401
|
||||
INVALID_PASSWORD=1402
|
||||
INVALID_USERNAME=1403
|
||||
INVALID_HASH_FUNCTION_NAME=1404
|
||||
INVALID_HASH_DIGGEST_LENGTH=1405
|
||||
INVALID_EMAIL_ADDRESS=1406
|
||||
INVALID_QUERY_PARAMETER_VALUE=1407
|
||||
TOO_MANY_RECIPIENTS_ON_EMAIL_LIST=1500
|
||||
|
||||
DEFAULT_QUOTA_LIMIT='2048'
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AppsForYourDomainException(Error):
|
||||
|
||||
def __init__(self, response):
|
||||
|
||||
Error.__init__(self, response)
|
||||
try:
|
||||
self.element_tree = ElementTree.fromstring(response['body'])
|
||||
self.error_code = int(self.element_tree[0].attrib['errorCode'])
|
||||
self.reason = self.element_tree[0].attrib['reason']
|
||||
self.invalidInput = self.element_tree[0].attrib['invalidInput']
|
||||
except:
|
||||
self.error_code = 600
|
||||
|
||||
class AppsService(gdata.service.GDataService):
|
||||
"""Client for the Google Apps Provisioning service."""
|
||||
|
||||
def __init__(self, email=None, password=None, domain=None, source=None,
|
||||
server='apps-apis.google.com', additional_headers=None,
|
||||
**kwargs):
|
||||
"""Creates a client for the Google Apps Provisioning service.
|
||||
|
||||
Args:
|
||||
email: string (optional) The user's email address, used for
|
||||
authentication.
|
||||
password: string (optional) The user's password.
|
||||
domain: string (optional) The Google Apps domain name.
|
||||
source: string (optional) The name of the user's application.
|
||||
server: string (optional) The name of the server to which a connection
|
||||
will be opened. Default value: 'apps-apis.google.com'.
|
||||
**kwargs: The other parameters to pass to gdata.service.GDataService
|
||||
constructor.
|
||||
"""
|
||||
gdata.service.GDataService.__init__(
|
||||
self, email=email, password=password, service='apps', source=source,
|
||||
server=server, additional_headers=additional_headers, **kwargs)
|
||||
self.ssl = True
|
||||
self.port = 443
|
||||
self.domain = domain
|
||||
|
||||
def _baseURL(self):
|
||||
return "/a/feeds/%s" % self.domain
|
||||
|
||||
def AddAllElementsFromAllPages(self, link_finder, func):
|
||||
"""retrieve all pages and add all elements"""
|
||||
next = link_finder.GetNextLink()
|
||||
while next is not None:
|
||||
next_feed = self.Get(next.href, converter=func)
|
||||
for a_entry in next_feed.entry:
|
||||
link_finder.entry.append(a_entry)
|
||||
next = next_feed.GetNextLink()
|
||||
return link_finder
|
||||
|
||||
def RetrievePageOfEmailLists(self, start_email_list_name=None,
|
||||
num_retries=gdata.service.DEFAULT_NUM_RETRIES,
|
||||
delay=gdata.service.DEFAULT_DELAY,
|
||||
backoff=gdata.service.DEFAULT_BACKOFF):
|
||||
"""Retrieve one page of email list"""
|
||||
uri = "%s/emailList/%s" % (self._baseURL(), API_VER)
|
||||
if start_email_list_name is not None:
|
||||
uri += "?startEmailListName=%s" % start_email_list_name
|
||||
try:
|
||||
return gdata.apps.EmailListFeedFromString(str(self.GetWithRetries(
|
||||
uri, num_retries=num_retries, delay=delay, backoff=backoff)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def GetGeneratorForAllEmailLists(
|
||||
self, num_retries=gdata.service.DEFAULT_NUM_RETRIES,
|
||||
delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF):
|
||||
"""Retrieve a generator for all emaillists in this domain."""
|
||||
first_page = self.RetrievePageOfEmailLists(num_retries=num_retries,
|
||||
delay=delay,
|
||||
backoff=backoff)
|
||||
return self.GetGeneratorFromLinkFinder(
|
||||
first_page, gdata.apps.EmailListRecipientFeedFromString,
|
||||
num_retries=num_retries, delay=delay, backoff=backoff)
|
||||
|
||||
def RetrieveAllEmailLists(self):
|
||||
"""Retrieve all email list of a domain."""
|
||||
|
||||
ret = self.RetrievePageOfEmailLists()
|
||||
# pagination
|
||||
return self.AddAllElementsFromAllPages(
|
||||
ret, gdata.apps.EmailListFeedFromString)
|
||||
|
||||
def RetrieveEmailList(self, list_name):
|
||||
"""Retreive a single email list by the list's name."""
|
||||
|
||||
uri = "%s/emailList/%s/%s" % (
|
||||
self._baseURL(), API_VER, list_name)
|
||||
try:
|
||||
return self.Get(uri, converter=gdata.apps.EmailListEntryFromString)
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def RetrieveEmailLists(self, recipient):
|
||||
"""Retrieve All Email List Subscriptions for an Email Address."""
|
||||
|
||||
uri = "%s/emailList/%s?recipient=%s" % (
|
||||
self._baseURL(), API_VER, recipient)
|
||||
try:
|
||||
ret = gdata.apps.EmailListFeedFromString(str(self.Get(uri)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
# pagination
|
||||
return self.AddAllElementsFromAllPages(
|
||||
ret, gdata.apps.EmailListFeedFromString)
|
||||
|
||||
def RemoveRecipientFromEmailList(self, recipient, list_name):
|
||||
"""Remove recipient from email list."""
|
||||
|
||||
uri = "%s/emailList/%s/%s/recipient/%s" % (
|
||||
self._baseURL(), API_VER, list_name, recipient)
|
||||
try:
|
||||
self.Delete(uri)
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def RetrievePageOfRecipients(self, list_name, start_recipient=None,
|
||||
num_retries=gdata.service.DEFAULT_NUM_RETRIES,
|
||||
delay=gdata.service.DEFAULT_DELAY,
|
||||
backoff=gdata.service.DEFAULT_BACKOFF):
|
||||
"""Retrieve one page of recipient of an email list. """
|
||||
|
||||
uri = "%s/emailList/%s/%s/recipient" % (
|
||||
self._baseURL(), API_VER, list_name)
|
||||
|
||||
if start_recipient is not None:
|
||||
uri += "?startRecipient=%s" % start_recipient
|
||||
try:
|
||||
return gdata.apps.EmailListRecipientFeedFromString(str(
|
||||
self.GetWithRetries(
|
||||
uri, num_retries=num_retries, delay=delay, backoff=backoff)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def GetGeneratorForAllRecipients(
|
||||
self, list_name, num_retries=gdata.service.DEFAULT_NUM_RETRIES,
|
||||
delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF):
|
||||
"""Retrieve a generator for all recipients of a particular emaillist."""
|
||||
first_page = self.RetrievePageOfRecipients(list_name,
|
||||
num_retries=num_retries,
|
||||
delay=delay,
|
||||
backoff=backoff)
|
||||
return self.GetGeneratorFromLinkFinder(
|
||||
first_page, gdata.apps.EmailListRecipientFeedFromString,
|
||||
num_retries=num_retries, delay=delay, backoff=backoff)
|
||||
|
||||
def RetrieveAllRecipients(self, list_name):
|
||||
"""Retrieve all recipient of an email list."""
|
||||
|
||||
ret = self.RetrievePageOfRecipients(list_name)
|
||||
# pagination
|
||||
return self.AddAllElementsFromAllPages(
|
||||
ret, gdata.apps.EmailListRecipientFeedFromString)
|
||||
|
||||
def AddRecipientToEmailList(self, recipient, list_name):
|
||||
"""Add a recipient to a email list."""
|
||||
|
||||
uri = "%s/emailList/%s/%s/recipient" % (
|
||||
self._baseURL(), API_VER, list_name)
|
||||
recipient_entry = gdata.apps.EmailListRecipientEntry()
|
||||
recipient_entry.who = gdata.apps.Who(email=recipient)
|
||||
|
||||
try:
|
||||
return gdata.apps.EmailListRecipientEntryFromString(
|
||||
str(self.Post(recipient_entry, uri)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def DeleteEmailList(self, list_name):
|
||||
"""Delete a email list"""
|
||||
|
||||
uri = "%s/emailList/%s/%s" % (self._baseURL(), API_VER, list_name)
|
||||
try:
|
||||
self.Delete(uri)
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def CreateEmailList(self, list_name):
|
||||
"""Create a email list. """
|
||||
|
||||
uri = "%s/emailList/%s" % (self._baseURL(), API_VER)
|
||||
email_list_entry = gdata.apps.EmailListEntry()
|
||||
email_list_entry.email_list = gdata.apps.EmailList(name=list_name)
|
||||
try:
|
||||
return gdata.apps.EmailListEntryFromString(
|
||||
str(self.Post(email_list_entry, uri)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def DeleteNickname(self, nickname):
|
||||
"""Delete a nickname"""
|
||||
|
||||
uri = "%s/nickname/%s/%s" % (self._baseURL(), API_VER, nickname)
|
||||
try:
|
||||
self.Delete(uri)
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def RetrievePageOfNicknames(self, start_nickname=None,
|
||||
num_retries=gdata.service.DEFAULT_NUM_RETRIES,
|
||||
delay=gdata.service.DEFAULT_DELAY,
|
||||
backoff=gdata.service.DEFAULT_BACKOFF):
|
||||
"""Retrieve one page of nicknames in the domain"""
|
||||
|
||||
uri = "%s/nickname/%s" % (self._baseURL(), API_VER)
|
||||
if start_nickname is not None:
|
||||
uri += "?startNickname=%s" % start_nickname
|
||||
try:
|
||||
return gdata.apps.NicknameFeedFromString(str(self.GetWithRetries(
|
||||
uri, num_retries=num_retries, delay=delay, backoff=backoff)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def GetGeneratorForAllNicknames(
|
||||
self, num_retries=gdata.service.DEFAULT_NUM_RETRIES,
|
||||
delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF):
|
||||
"""Retrieve a generator for all nicknames in this domain."""
|
||||
first_page = self.RetrievePageOfNicknames(num_retries=num_retries,
|
||||
delay=delay,
|
||||
backoff=backoff)
|
||||
return self.GetGeneratorFromLinkFinder(
|
||||
first_page, gdata.apps.NicknameFeedFromString, num_retries=num_retries,
|
||||
delay=delay, backoff=backoff)
|
||||
|
||||
def RetrieveAllNicknames(self):
|
||||
"""Retrieve all nicknames in the domain"""
|
||||
|
||||
ret = self.RetrievePageOfNicknames()
|
||||
# pagination
|
||||
return self.AddAllElementsFromAllPages(
|
||||
ret, gdata.apps.NicknameFeedFromString)
|
||||
|
||||
def GetGeneratorForAllNicknamesOfAUser(
|
||||
self, user_name, num_retries=gdata.service.DEFAULT_NUM_RETRIES,
|
||||
delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF):
|
||||
"""Retrieve a generator for all nicknames of a particular user."""
|
||||
uri = "%s/nickname/%s?username=%s" % (self._baseURL(), API_VER, user_name)
|
||||
try:
|
||||
first_page = gdata.apps.NicknameFeedFromString(str(self.GetWithRetries(
|
||||
uri, num_retries=num_retries, delay=delay, backoff=backoff)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
return self.GetGeneratorFromLinkFinder(
|
||||
first_page, gdata.apps.NicknameFeedFromString, num_retries=num_retries,
|
||||
delay=delay, backoff=backoff)
|
||||
|
||||
def RetrieveNicknames(self, user_name):
|
||||
"""Retrieve nicknames of the user"""
|
||||
|
||||
uri = "%s/nickname/%s?username=%s" % (self._baseURL(), API_VER, user_name)
|
||||
try:
|
||||
ret = gdata.apps.NicknameFeedFromString(str(self.Get(uri)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
# pagination
|
||||
return self.AddAllElementsFromAllPages(
|
||||
ret, gdata.apps.NicknameFeedFromString)
|
||||
|
||||
def RetrieveNickname(self, nickname):
|
||||
"""Retrieve a nickname.
|
||||
|
||||
Args:
|
||||
nickname: string The nickname to retrieve
|
||||
|
||||
Returns:
|
||||
gdata.apps.NicknameEntry
|
||||
"""
|
||||
|
||||
uri = "%s/nickname/%s/%s" % (self._baseURL(), API_VER, nickname)
|
||||
try:
|
||||
return gdata.apps.NicknameEntryFromString(str(self.Get(uri)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def CreateNickname(self, user_name, nickname):
|
||||
"""Create a nickname"""
|
||||
|
||||
uri = "%s/nickname/%s" % (self._baseURL(), API_VER)
|
||||
nickname_entry = gdata.apps.NicknameEntry()
|
||||
nickname_entry.login = gdata.apps.Login(user_name=user_name)
|
||||
nickname_entry.nickname = gdata.apps.Nickname(name=nickname)
|
||||
|
||||
try:
|
||||
return gdata.apps.NicknameEntryFromString(
|
||||
str(self.Post(nickname_entry, uri)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def DeleteUser(self, user_name):
|
||||
"""Delete a user account"""
|
||||
|
||||
uri = "%s/user/%s/%s" % (self._baseURL(), API_VER, user_name)
|
||||
try:
|
||||
return self.Delete(uri)
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def UpdateUser(self, user_name, user_entry):
|
||||
"""Update a user account."""
|
||||
|
||||
uri = "%s/user/%s/%s" % (self._baseURL(), API_VER, user_name)
|
||||
try:
|
||||
return gdata.apps.UserEntryFromString(str(self.Put(user_entry, uri)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def CreateUser(self, user_name, family_name, given_name, password,
|
||||
suspended='false', quota_limit=None,
|
||||
password_hash_function=None,
|
||||
change_password=None):
|
||||
"""Create a user account. """
|
||||
|
||||
uri = "%s/user/%s" % (self._baseURL(), API_VER)
|
||||
user_entry = gdata.apps.UserEntry()
|
||||
user_entry.login = gdata.apps.Login(
|
||||
user_name=user_name, password=password, suspended=suspended,
|
||||
hash_function_name=password_hash_function,
|
||||
change_password=change_password)
|
||||
user_entry.name = gdata.apps.Name(family_name=family_name,
|
||||
given_name=given_name)
|
||||
if quota_limit is not None:
|
||||
user_entry.quota = gdata.apps.Quota(limit=str(quota_limit))
|
||||
|
||||
try:
|
||||
return gdata.apps.UserEntryFromString(str(self.Post(user_entry, uri)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def SuspendUser(self, user_name):
|
||||
user_entry = self.RetrieveUser(user_name)
|
||||
if user_entry.login.suspended != 'true':
|
||||
user_entry.login.suspended = 'true'
|
||||
user_entry = self.UpdateUser(user_name, user_entry)
|
||||
return user_entry
|
||||
|
||||
def RestoreUser(self, user_name):
|
||||
user_entry = self.RetrieveUser(user_name)
|
||||
if user_entry.login.suspended != 'false':
|
||||
user_entry.login.suspended = 'false'
|
||||
user_entry = self.UpdateUser(user_name, user_entry)
|
||||
return user_entry
|
||||
|
||||
def RetrieveUser(self, user_name):
|
||||
"""Retrieve an user account.
|
||||
|
||||
Args:
|
||||
user_name: string The user name to retrieve
|
||||
|
||||
Returns:
|
||||
gdata.apps.UserEntry
|
||||
"""
|
||||
|
||||
uri = "%s/user/%s/%s" % (self._baseURL(), API_VER, user_name)
|
||||
try:
|
||||
return gdata.apps.UserEntryFromString(str(self.Get(uri)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def RetrievePageOfUsers(self, start_username=None,
|
||||
num_retries=gdata.service.DEFAULT_NUM_RETRIES,
|
||||
delay=gdata.service.DEFAULT_DELAY,
|
||||
backoff=gdata.service.DEFAULT_BACKOFF):
|
||||
"""Retrieve one page of users in this domain."""
|
||||
|
||||
uri = "%s/user/%s" % (self._baseURL(), API_VER)
|
||||
if start_username is not None:
|
||||
uri += "?startUsername=%s" % start_username
|
||||
try:
|
||||
return gdata.apps.UserFeedFromString(str(self.GetWithRetries(
|
||||
uri, num_retries=num_retries, delay=delay, backoff=backoff)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def GetGeneratorForAllUsers(self,
|
||||
num_retries=gdata.service.DEFAULT_NUM_RETRIES,
|
||||
delay=gdata.service.DEFAULT_DELAY,
|
||||
backoff=gdata.service.DEFAULT_BACKOFF):
|
||||
"""Retrieve a generator for all users in this domain."""
|
||||
first_page = self.RetrievePageOfUsers(num_retries=num_retries, delay=delay,
|
||||
backoff=backoff)
|
||||
return self.GetGeneratorFromLinkFinder(
|
||||
first_page, gdata.apps.UserFeedFromString, num_retries=num_retries,
|
||||
delay=delay, backoff=backoff)
|
||||
|
||||
def RetrieveAllUsers(self):
|
||||
"""Retrieve all users in this domain. OBSOLETE"""
|
||||
|
||||
ret = self.RetrievePageOfUsers()
|
||||
# pagination
|
||||
return self.AddAllElementsFromAllPages(
|
||||
ret, gdata.apps.UserFeedFromString)
|
||||
|
||||
|
||||
class PropertyService(gdata.service.GDataService):
|
||||
"""Client for the Google Apps Property service."""
|
||||
|
||||
def __init__(self, email=None, password=None, domain=None, source=None,
|
||||
server='apps-apis.google.com', additional_headers=None):
|
||||
gdata.service.GDataService.__init__(self, email=email, password=password,
|
||||
service='apps', source=source,
|
||||
server=server,
|
||||
additional_headers=additional_headers)
|
||||
self.ssl = True
|
||||
self.port = 443
|
||||
self.domain = domain
|
||||
|
||||
def AddAllElementsFromAllPages(self, link_finder, func):
|
||||
"""retrieve all pages and add all elements"""
|
||||
next = link_finder.GetNextLink()
|
||||
count = 0
|
||||
while next is not None:
|
||||
next_feed = self.Get(next.href, converter=func)
|
||||
count = count + len(next_feed.entry)
|
||||
for a_entry in next_feed.entry:
|
||||
link_finder.entry.append(a_entry)
|
||||
next = next_feed.GetNextLink()
|
||||
return link_finder
|
||||
|
||||
def _GetPropertyEntry(self, properties):
|
||||
property_entry = gdata.apps.PropertyEntry()
|
||||
property = []
|
||||
for name, value in properties.items():
|
||||
if name is not None and value is not None:
|
||||
property.append(gdata.apps.Property(name=name, value=value))
|
||||
property_entry.property = property
|
||||
return property_entry
|
||||
|
||||
def _PropertyEntry2Dict(self, property_entry):
|
||||
properties = {}
|
||||
for i, property in enumerate(property_entry.property):
|
||||
properties[property.name] = property.value
|
||||
return properties
|
||||
|
||||
def _GetPropertyFeed(self, uri):
|
||||
try:
|
||||
return gdata.apps.PropertyFeedFromString(str(self.Get(uri)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def _GetPropertiesList(self, uri):
|
||||
property_feed = self._GetPropertyFeed(uri)
|
||||
# pagination
|
||||
property_feed = self.AddAllElementsFromAllPages(
|
||||
property_feed, gdata.apps.PropertyFeedFromString)
|
||||
properties_list = []
|
||||
for property_entry in property_feed.entry:
|
||||
properties_list.append(self._PropertyEntry2Dict(property_entry))
|
||||
return properties_list
|
||||
|
||||
def _GetProperties(self, uri):
|
||||
try:
|
||||
return self._PropertyEntry2Dict(gdata.apps.PropertyEntryFromString(
|
||||
str(self.Get(uri))))
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def _PostProperties(self, uri, properties):
|
||||
property_entry = self._GetPropertyEntry(properties)
|
||||
try:
|
||||
return self._PropertyEntry2Dict(gdata.apps.PropertyEntryFromString(
|
||||
str(self.Post(property_entry, uri))))
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def _PutProperties(self, uri, properties):
|
||||
property_entry = self._GetPropertyEntry(properties)
|
||||
try:
|
||||
return self._PropertyEntry2Dict(gdata.apps.PropertyEntryFromString(
|
||||
str(self.Put(property_entry, uri))))
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def _DeleteProperties(self, uri):
|
||||
try:
|
||||
self.Delete(uri)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
|
||||
def _bool2str(b):
|
||||
if b is None:
|
||||
return None
|
||||
return str(b is True).lower()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,247 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright (C) 2008 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
"""Provides HTTP functions for gdata.service to use on Google App Engine
|
||||
|
||||
AppEngineHttpClient: Provides an HTTP request method which uses App Engine's
|
||||
urlfetch API. Set the http_client member of a GDataService object to an
|
||||
instance of an AppEngineHttpClient to allow the gdata library to run on
|
||||
Google App Engine.
|
||||
|
||||
run_on_appengine: Function which will modify an existing GDataService object
|
||||
to allow it to run on App Engine. It works by creating a new instance of
|
||||
the AppEngineHttpClient and replacing the GDataService object's
|
||||
http_client.
|
||||
|
||||
HttpRequest: Function that wraps google.appengine.api.urlfetch.Fetch in a
|
||||
common interface which is used by gdata.service.GDataService. In other
|
||||
words, this module can be used as the gdata service request handler so
|
||||
that all HTTP requests will be performed by the hosting Google App Engine
|
||||
server.
|
||||
"""
|
||||
|
||||
|
||||
__author__ = 'api.jscudder (Jeff Scudder)'
|
||||
|
||||
|
||||
import io
|
||||
import atom.service
|
||||
import atom.http_interface
|
||||
from google.appengine.api import urlfetch
|
||||
|
||||
|
||||
def run_on_appengine(gdata_service):
|
||||
"""Modifies a GDataService object to allow it to run on App Engine.
|
||||
|
||||
Args:
|
||||
gdata_service: An instance of AtomService, GDataService, or any
|
||||
of their subclasses which has an http_client member.
|
||||
"""
|
||||
gdata_service.http_client = AppEngineHttpClient()
|
||||
|
||||
|
||||
class AppEngineHttpClient(atom.http_interface.GenericHttpClient):
|
||||
def __init__(self, headers=None):
|
||||
self.debug = False
|
||||
self.headers = headers or {}
|
||||
|
||||
def request(self, operation, url, data=None, headers=None):
|
||||
"""Performs an HTTP call to the server, supports GET, POST, PUT, and
|
||||
DELETE.
|
||||
|
||||
Usage example, perform and HTTP GET on http://www.google.com/:
|
||||
import atom.http
|
||||
client = atom.http.HttpClient()
|
||||
http_response = client.request('GET', 'http://www.google.com/')
|
||||
|
||||
Args:
|
||||
operation: str The HTTP operation to be performed. This is usually one
|
||||
of 'GET', 'POST', 'PUT', or 'DELETE'
|
||||
data: filestream, list of parts, or other object which can be converted
|
||||
to a string. Should be set to None when performing a GET or DELETE.
|
||||
If data is a file-like object which can be read, this method will
|
||||
read a chunk of 100K bytes at a time and send them.
|
||||
If the data is a list of parts to be sent, each part will be
|
||||
evaluated and sent.
|
||||
url: The full URL to which the request should be sent. Can be a string
|
||||
or atom.url.Url.
|
||||
headers: dict of strings. HTTP headers which should be sent
|
||||
in the request.
|
||||
"""
|
||||
all_headers = self.headers.copy()
|
||||
if headers:
|
||||
all_headers.update(headers)
|
||||
|
||||
# Construct the full payload.
|
||||
# Assume that data is None or a string.
|
||||
data_str = data
|
||||
if data:
|
||||
if isinstance(data, list):
|
||||
# If data is a list of different objects, convert them all to strings
|
||||
# and join them together.
|
||||
converted_parts = [__ConvertDataPart(x) for x in data]
|
||||
data_str = ''.join(converted_parts)
|
||||
else:
|
||||
data_str = __ConvertDataPart(data)
|
||||
|
||||
# If the list of headers does not include a Content-Length, attempt to
|
||||
# calculate it based on the data object.
|
||||
if data and 'Content-Length' not in all_headers:
|
||||
all_headers['Content-Length'] = len(data_str)
|
||||
|
||||
# Set the content type to the default value if none was set.
|
||||
if 'Content-Type' not in all_headers:
|
||||
all_headers['Content-Type'] = 'application/atom+xml'
|
||||
|
||||
# Lookup the urlfetch operation which corresponds to the desired HTTP verb.
|
||||
if operation == 'GET':
|
||||
method = urlfetch.GET
|
||||
elif operation == 'POST':
|
||||
method = urlfetch.POST
|
||||
elif operation == 'PUT':
|
||||
method = urlfetch.PUT
|
||||
elif operation == 'DELETE':
|
||||
method = urlfetch.DELETE
|
||||
else:
|
||||
method = None
|
||||
return HttpResponse(urlfetch.Fetch(url=str(url), payload=data_str,
|
||||
method=method, headers=all_headers))
|
||||
|
||||
|
||||
def HttpRequest(service, operation, data, uri, extra_headers=None,
|
||||
url_params=None, escape_params=True, content_type='application/atom+xml'):
|
||||
"""Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE.
|
||||
|
||||
This function is deprecated, use AppEngineHttpClient.request instead.
|
||||
|
||||
To use this module with gdata.service, you can set this module to be the
|
||||
http_request_handler so that HTTP requests use Google App Engine's urlfetch.
|
||||
import gdata.service
|
||||
import gdata.urlfetch
|
||||
gdata.service.http_request_handler = gdata.urlfetch
|
||||
|
||||
Args:
|
||||
service: atom.AtomService object which contains some of the parameters
|
||||
needed to make the request. The following members are used to
|
||||
construct the HTTP call: server (str), additional_headers (dict),
|
||||
port (int), and ssl (bool).
|
||||
operation: str The HTTP operation to be performed. This is usually one of
|
||||
'GET', 'POST', 'PUT', or 'DELETE'
|
||||
data: filestream, list of parts, or other object which can be
|
||||
converted to a string.
|
||||
Should be set to None when performing a GET or PUT.
|
||||
If data is a file-like object which can be read, this method will read
|
||||
a chunk of 100K bytes at a time and send them.
|
||||
If the data is a list of parts to be sent, each part will be evaluated
|
||||
and sent.
|
||||
uri: The beginning of the URL to which the request should be sent.
|
||||
Examples: '/', '/base/feeds/snippets',
|
||||
'/m8/feeds/contacts/default/base'
|
||||
extra_headers: dict of strings. HTTP headers which should be sent
|
||||
in the request. These headers are in addition to those stored in
|
||||
service.additional_headers.
|
||||
url_params: dict of strings. Key value pairs to be added to the URL as
|
||||
URL parameters. For example {'foo':'bar', 'test':'param'} will
|
||||
become ?foo=bar&test=param.
|
||||
escape_params: bool default True. If true, the keys and values in
|
||||
url_params will be URL escaped when the form is constructed
|
||||
(Special characters converted to %XX form.)
|
||||
content_type: str The MIME type for the data being sent. Defaults to
|
||||
'application/atom+xml', this is only used if data is set.
|
||||
"""
|
||||
full_uri = atom.service.BuildUri(uri, url_params, escape_params)
|
||||
(server, port, ssl, partial_uri) = atom.service.ProcessUrl(service, full_uri)
|
||||
# Construct the full URL for the request.
|
||||
if ssl:
|
||||
full_url = 'https://%s%s' % (server, partial_uri)
|
||||
else:
|
||||
full_url = 'http://%s%s' % (server, partial_uri)
|
||||
|
||||
# Construct the full payload.
|
||||
# Assume that data is None or a string.
|
||||
data_str = data
|
||||
if data:
|
||||
if isinstance(data, list):
|
||||
# If data is a list of different objects, convert them all to strings
|
||||
# and join them together.
|
||||
converted_parts = [__ConvertDataPart(x) for x in data]
|
||||
data_str = ''.join(converted_parts)
|
||||
else:
|
||||
data_str = __ConvertDataPart(data)
|
||||
|
||||
# Construct the dictionary of HTTP headers.
|
||||
headers = {}
|
||||
if isinstance(service.additional_headers, dict):
|
||||
headers = service.additional_headers.copy()
|
||||
if isinstance(extra_headers, dict):
|
||||
for header, value in extra_headers.items():
|
||||
headers[header] = value
|
||||
# Add the content type header (we don't need to calculate content length,
|
||||
# since urlfetch.Fetch will calculate for us).
|
||||
if content_type:
|
||||
headers['Content-Type'] = content_type
|
||||
|
||||
# Lookup the urlfetch operation which corresponds to the desired HTTP verb.
|
||||
if operation == 'GET':
|
||||
method = urlfetch.GET
|
||||
elif operation == 'POST':
|
||||
method = urlfetch.POST
|
||||
elif operation == 'PUT':
|
||||
method = urlfetch.PUT
|
||||
elif operation == 'DELETE':
|
||||
method = urlfetch.DELETE
|
||||
else:
|
||||
method = None
|
||||
return HttpResponse(urlfetch.Fetch(url=full_url, payload=data_str,
|
||||
method=method, headers=headers))
|
||||
|
||||
|
||||
def __ConvertDataPart(data):
|
||||
if not data or isinstance(data, str):
|
||||
return data
|
||||
elif hasattr(data, 'read'):
|
||||
# data is a file like object, so read it completely.
|
||||
return data.read()
|
||||
# The data object was not a file.
|
||||
# Try to convert to a string and send the data.
|
||||
return str(data)
|
||||
|
||||
|
||||
class HttpResponse(object):
|
||||
"""Translates a urlfetch resoinse to look like an hhtplib resoinse.
|
||||
|
||||
Used to allow the resoinse from HttpRequest to be usable by gdata.service
|
||||
methods.
|
||||
"""
|
||||
|
||||
def __init__(self, urlfetch_response):
|
||||
self.body = io.StringIO(urlfetch_response.content)
|
||||
self.headers = urlfetch_response.headers
|
||||
self.status = urlfetch_response.status_code
|
||||
self.reason = ''
|
||||
|
||||
def read(self, length=None):
|
||||
if not length:
|
||||
return self.body.read()
|
||||
else:
|
||||
return self.body.read(length)
|
||||
|
||||
def getheader(self, name):
|
||||
if name not in self.headers:
|
||||
return self.headers[name.lower()]
|
||||
return self.headers[name]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,141 +0,0 @@
|
||||
{
|
||||
"basePath": "",
|
||||
"baseUrl": "https://www.googleapis.com/service_accounts/v1",
|
||||
"canonicalName": "serviceaccountlookup",
|
||||
"description": "Pseudo-API to lookup public certificates for a service account anonymously",
|
||||
"discoveryVersion": "v1",
|
||||
"documentationLink": "https://example.com/",
|
||||
"fullyEncodeReservedExpansion": true,
|
||||
"icons": {
|
||||
"x16": "http://www.google.com/images/icons/product/search-16.gif",
|
||||
"x32": "http://www.google.com/images/icons/product/search-32.gif"
|
||||
},
|
||||
"id": "serviceaccountlookup:v1",
|
||||
"kind": "discovery#restDescription",
|
||||
"name": "serviceaccountlookup",
|
||||
"ownerDomain": "google.com",
|
||||
"ownerName": "Google",
|
||||
"packagePath": "admin",
|
||||
"parameters": {
|
||||
"$.xgafv": {
|
||||
"description": "V1 error format.",
|
||||
"enum": [
|
||||
"1",
|
||||
"2"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"v1 error format",
|
||||
"v2 error format"
|
||||
],
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"access_token": {
|
||||
"description": "OAuth access token.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"alt": {
|
||||
"default": "json",
|
||||
"description": "Data format for response.",
|
||||
"enum": [
|
||||
"json",
|
||||
"media",
|
||||
"proto"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Responses with Content-Type of application/json",
|
||||
"Media download with context-dependent Content-Type",
|
||||
"Responses with Content-Type of application/x-protobuf"
|
||||
],
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"callback": {
|
||||
"description": "JSONP",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"fields": {
|
||||
"description": "Selector specifying which fields to include in a partial response.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"oauth_token": {
|
||||
"description": "OAuth 2.0 token for the current user.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"prettyPrint": {
|
||||
"default": "true",
|
||||
"description": "Returns response with indentations and line breaks.",
|
||||
"location": "query",
|
||||
"type": "boolean"
|
||||
},
|
||||
"quotaUser": {
|
||||
"description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"uploadType": {
|
||||
"description": "Legacy upload protocol for media (e.g. \"media\", \"multipart\").",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"upload_protocol": {
|
||||
"description": "Upload protocol for media (e.g. \"raw\", \"multipart\").",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"protocol": "rest",
|
||||
"resources": {
|
||||
"serviceaccounts": {
|
||||
"methods": {
|
||||
"lookup": {
|
||||
"description": "Lookup",
|
||||
"flatPath": "metadata/x509/{account}",
|
||||
"httpMethod": "GET",
|
||||
"id": "serviceaccountslookup.lookup",
|
||||
"parameterOrder": [
|
||||
"account"
|
||||
],
|
||||
"parameters": {
|
||||
"account": {
|
||||
"description": "Email or ID of the service account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "metadata/x509/{account}",
|
||||
"response": {
|
||||
"$ref": "Certificates"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"rootUrl": "https://www.googleapis.com/service_accounts/v1",
|
||||
"schemas": {
|
||||
"Certificates": {
|
||||
"description": "JSON template for certificates.",
|
||||
"id": "Certificates",
|
||||
"properties": {
|
||||
"email": { "description": "Email of the delegate.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"servicePath": "",
|
||||
"title": "Service Account Lookup Pseudo-API",
|
||||
"version": "v1",
|
||||
"version_module": true
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2822
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fswiss\fcharset0 Helvetica-Bold;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
{\*\expandedcolortbl;;}
|
||||
\vieww12000\viewh15840\viewkind0
|
||||
\deftab720
|
||||
\pard\pardeftab720\sl276\slmult1\sa200\qc\partightenfactor0
|
||||
|
||||
\f0\fs22 \cf0 Copyright 2026 Jay Lee\
|
||||
\pard\pardeftab720\sa200\qc\partightenfactor0
|
||||
|
||||
\f1\b \cf0 Licensed under the Apache License, Version 2.0 (the "License");\
|
||||
\pard\pardeftab720\sl276\slmult1\sa200\qc\partightenfactor0
|
||||
|
||||
\f0\b0 \cf0 you may not use this file except in compliance with the License.\
|
||||
You may obtain a copy of the License at\
|
||||
\
|
||||
{\field{\*\fldinst{HYPERLINK "http://www.apache.org/licenses/LICENSE-2.0"}}{\fldrslt http://www.apache.org/licenses/LICENSE-2.0}}\
|
||||
\
|
||||
Unless required by applicable law or agreed to in writing, software\
|
||||
distributed under the License is distributed on an "AS IS" BASIS,\
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\
|
||||
See the License for the specific language governing permissions and\
|
||||
limitations under the License.\
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|ARM">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="PGInstrument|ARM">
|
||||
<Configuration>PGInstrument</Configuration>
|
||||
<Platform>ARM</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="PGInstrument|ARM64">
|
||||
<Configuration>PGInstrument</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="PGInstrument|Win32">
|
||||
<Configuration>PGInstrument</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="PGInstrument|x64">
|
||||
<Configuration>PGInstrument</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="PGUpdate|ARM">
|
||||
<Configuration>PGUpdate</Configuration>
|
||||
<Platform>ARM</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="PGUpdate|ARM64">
|
||||
<Configuration>PGUpdate</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="PGUpdate|Win32">
|
||||
<Configuration>PGUpdate</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="PGUpdate|x64">
|
||||
<Configuration>PGUpdate</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{447F05A8-F581-4CAC-A466-5AC7936E207E}</ProjectGuid>
|
||||
<RootNamespace>_hashlib</RootNamespace>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
</PropertyGroup>
|
||||
<Import Project="python.props" />
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<PropertyGroup>
|
||||
<TargetExt>$(PyStdlibPydExt)</TargetExt>
|
||||
</PropertyGroup>
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="pyproject.props" />
|
||||
<Import Project="openssl.props" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<Link>
|
||||
<AdditionalDependencies>ws2_32.lib;crypt32.lib;advapi32.lib;user32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\Modules\_hashopenssl.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="..\PC\python_nt.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="pythoncore.vcxproj">
|
||||
<Project>{cf7ac3d1-e2df-41d2-bea6-1e2556cdea26}</Project>
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from packaging import version
|
||||
import sys
|
||||
|
||||
a = sys.argv[1]
|
||||
b = sys.argv[2]
|
||||
result = version.parse(a) >= version.parse(b)
|
||||
if result:
|
||||
print('OK: %s is equal or newer than %s' % (a, b))
|
||||
else:
|
||||
print('ERROR: %s is older than %s' % (a, b))
|
||||
sys.exit(not result)
|
||||
@@ -1,23 +0,0 @@
|
||||
import uuid
|
||||
from lxml import etree
|
||||
import sys
|
||||
|
||||
# Hacky solution to create a Guid for all files
|
||||
# so Wix is happy and Guid is stable every time.
|
||||
# uuid5 is used for the Guid and the input is the
|
||||
# source filename so the Guid will be the same
|
||||
# every time as long as the source file name is
|
||||
# the same.
|
||||
|
||||
rewrite_file = sys.argv[1]
|
||||
|
||||
with open(rewrite_file, 'rb') as f:
|
||||
input_xml = f.read()
|
||||
root = etree.fromstring(input_xml)
|
||||
for elem in root.getiterator():
|
||||
if 'Guid' in elem.attrib:
|
||||
source = elem.getchildren()[0].attrib['Source']
|
||||
stable_uuid = str(uuid.uuid5(uuid.NAMESPACE_URL, source))
|
||||
elem.attrib['Guid'] = stable_uuid
|
||||
with open(rewrite_file, 'w') as f:
|
||||
f.write(etree.tostring(root).decode())
|
||||
@@ -1,21 +0,0 @@
|
||||
# ------------------------------------------------------------------
|
||||
# Copyright (c) 2021 PyInstaller Development Team.
|
||||
#
|
||||
# This file is distributed under the terms of the GNU General Public
|
||||
# License (version 2.0 or later).
|
||||
#
|
||||
# The full license is available in LICENSE, distributed with
|
||||
# this software.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
from PyInstaller.utils.hooks import copy_metadata
|
||||
from PyInstaller.utils.hooks import collect_data_files
|
||||
|
||||
# googleapiclient.model queries the library version via
|
||||
# pkg_resources.get_distribution("google-api-python-client").version,
|
||||
# so we need to collect that package's metadata
|
||||
datas = copy_metadata('google_api_python_client')
|
||||
# we don't want these cached discovery files and they make the binary HUUUGEEEE
|
||||
#datas += collect_data_files('googleapiclient.discovery_cache', excludes=['*.txt', '**/__pycache__'])
|
||||
@@ -1,19 +0,0 @@
|
||||
# ------------------------------------------------------------------
|
||||
# Copyright (c) 2020 PyInstaller Development Team.
|
||||
#
|
||||
# This file is distributed under the terms of the GNU General Public
|
||||
# License (version 2.0 or later).
|
||||
#
|
||||
# The full license is available in LICENSE, distributed with
|
||||
# this software.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
# This is needed to bundle cacerts.txt that comes with httplib2 module
|
||||
|
||||
# WE DON'T NEED httplib2/cacerts.txt since we get our own
|
||||
|
||||
#from PyInstaller.utils.hooks import collect_data_files
|
||||
|
||||
#datas = collect_data_files('httplib2')
|
||||
@@ -1,246 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import re
|
||||
import sys
|
||||
from collections import OrderedDict
|
||||
|
||||
# A simple, quick & dirty script to parse GamCommands.txt and
|
||||
# produce HTML with parameter references in commands linked to their
|
||||
# definitions.
|
||||
#
|
||||
# We expect the following grammar:
|
||||
#
|
||||
# ^gam.* Beginning of a command
|
||||
# <(.*?)>\s+::= Beginning of a parameter BNF production
|
||||
#
|
||||
# Input lines immediately following a command or parameter are
|
||||
# accumulated until encountering a blank line or the next command or
|
||||
# parameter. This is implemented as a state machine.
|
||||
#
|
||||
# In the output HTML, every parameter definition is given an id=
|
||||
# attribute (__x where x is the parameter name) and references in
|
||||
# commands and RHS of parameter productions are links pointing to the
|
||||
# definition.
|
||||
#
|
||||
# Input is syntax-checked to verify matching angle-brackets. If
|
||||
# an entry fails this test it is discarded and a message is written
|
||||
# to stderr.
|
||||
#
|
||||
# There are also 3 special cases handled separately since they don't
|
||||
# fit this grammar. See 'specialCases' below.
|
||||
#
|
||||
# If a parameter (BNF production) duplicates one we've already seen, we
|
||||
# ignore it, and if it isn't identical to the existing one we write a
|
||||
# message to stderr.
|
||||
#
|
||||
# Written with python 3.7, explaining non-use of lookahead/lookbehind
|
||||
#
|
||||
# Typical usage:
|
||||
#
|
||||
# python mkGamRef.py GamCommands.txt > GamCommands.html
|
||||
#
|
||||
|
||||
reCommand = re.compile(r'gam ') # Start of command
|
||||
reProduction = re.compile(r'<(.*?)>\s+::=') # Start of parameter BNF production
|
||||
reBlankLine = re.compile(r'\s*') # Totally blank line
|
||||
reWhitespace = re.compile(r'\s+') # To detect continuations for HTML <br/>
|
||||
reProdRef = re.compile(r'<(.*?)>') # Cross-reference to a parameter
|
||||
reBrackets = re.compile(r'[<>]') # For counting brackets
|
||||
reBadLink1 = re.compile(r'[/a-zA-Z]+>') # Pin down missing opening bracket
|
||||
reBadLink2 = re.compile(r'<[/a-zA-Z]+') # Pin down missing closing bracket
|
||||
|
||||
# Special cases of input that get "fixed" on input because
|
||||
# they don't follow the grammar used everywhere else
|
||||
specialCases = [
|
||||
[re.compile(r'<(Number in range )(\d+)-(\d+)>'), r'<\1\2-\3>'],
|
||||
['<|<=|>=|>|=|!=', '< | <= | >= | > | = | !='],
|
||||
['<RRULE, EXRULE, RDATE and EXDATE line>', '<RRULE, EXRULE, RDATE and EXDATE line>'] ]
|
||||
|
||||
# List of gam commands. Each entry is a list of input lines
|
||||
commands = []
|
||||
|
||||
# OrderedDict of parameter BNF productions
|
||||
# key: non-terminal name
|
||||
# value: List of input lines
|
||||
productions = OrderedDict()
|
||||
|
||||
# Simple HTML wrapper
|
||||
prefix = '''\
|
||||
<html>
|
||||
<head></head>
|
||||
<body style="font-size:large; font-family:monospace">
|
||||
<p><a href="#_commands">Commands</a>
|
||||
<p><a href="#_parameters">Parameters</a>
|
||||
'''
|
||||
|
||||
suffix = '''\
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
# Examine an input line and return a token type, extracted
|
||||
# production key (if this is the first line of a production)
|
||||
# and the line text, all as a tuple
|
||||
# Token Types:
|
||||
# 0 - Blank line
|
||||
# 1 - First line of gam command
|
||||
# 2 - First line of BNF parameter production
|
||||
# 3 - Everything else
|
||||
def examine(line):
|
||||
if reBlankLine.fullmatch(line):
|
||||
return (0, '', '')
|
||||
|
||||
m = reCommand.match(line)
|
||||
if m:
|
||||
return (1, '', line)
|
||||
|
||||
m = reProduction.match(line)
|
||||
if m:
|
||||
return (2, m.group(1), line)
|
||||
|
||||
return (3, '', line)
|
||||
|
||||
# Save the current command or production
|
||||
# Detect duplicate BNF productions and write an error message
|
||||
# if not identical to the one already on file
|
||||
def save(state,key,data):
|
||||
global commands, productions
|
||||
if state==1:
|
||||
commands += [data]
|
||||
elif state ==2:
|
||||
if key in productions:
|
||||
if data == productions[key]:
|
||||
#sys.stderr.write(f"Identical duplicate production {key} ignored\n")
|
||||
pass
|
||||
else:
|
||||
sys.stderr.write(f"Conflicting duplicate production {key} ignored\n")
|
||||
else:
|
||||
productions[key] = data
|
||||
|
||||
# Detect and fix special cases
|
||||
def fixSpecialCases(line):
|
||||
for c in specialCases:
|
||||
# c is a tuple (search, replacement)
|
||||
# search can be a re.Pattern, in which case replacement is a regex sub with backreferences
|
||||
# search can be a string, in which case replacement is a direct substitution
|
||||
if isinstance(c[0], re.Pattern):
|
||||
if c[0].search(line):
|
||||
line = c[0].sub(c[1],line)
|
||||
else:
|
||||
pos = line.find(c[0])
|
||||
if pos >= 0:
|
||||
line = line[:pos] + c[1] + line[pos+len(c[1]):]
|
||||
return line
|
||||
|
||||
# Write the HTML for one complete command or production,
|
||||
# generating the cross-references from command parameters to
|
||||
# the parameter definitions (the BNF productions)
|
||||
def resolve(data,id=None):
|
||||
if id:
|
||||
result = f'<div id="__{id}" style="padding:12px; padding-left:100px; text-indent:-90px">'
|
||||
else:
|
||||
result = f'<div style="padding:12px; padding-left:100px; text-indent:-90px">'
|
||||
|
||||
for line in data:
|
||||
if reWhitespace.match(line):
|
||||
result += "<br/>"
|
||||
result += reProdRef.sub(r'<a href="#__\1"><\1></a>', line)
|
||||
|
||||
result += "</div>"
|
||||
return result
|
||||
|
||||
# Syntax check an input line
|
||||
def validate(line):
|
||||
|
||||
# Check bracket matching/nesting (no nesting allowed)
|
||||
blist = reBrackets.findall(line)
|
||||
depth = 0
|
||||
nnest = False
|
||||
for c in blist:
|
||||
if c == '<': depth += 1
|
||||
elif c == '>': depth -= 1
|
||||
if depth > 1: nnest = True
|
||||
|
||||
if depth != 0 or nnest:
|
||||
return False
|
||||
|
||||
# Look for bracket syntax errors
|
||||
for m in reBadLink1.finditer(line):
|
||||
if m.start() == 0 or line[m.start()-1] != '<':
|
||||
return False
|
||||
|
||||
for m in reBadLink2.finditer(line):
|
||||
if m.end() >= len(line) or line[m.end()] != '>':
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# Simple Finite State Machine to parse GamCommands.txt
|
||||
#
|
||||
# C u r r e n t S t a t e
|
||||
# 0 1 2
|
||||
# Input Token Start gam <BNF>
|
||||
# ------------ ----- --- -----
|
||||
# 0 empty Line - s0 s0
|
||||
# 1 gam 1+ s1+ s1+
|
||||
# 2 <BNF> 2+ s2+ s2+
|
||||
# 3 all other - 1+ 2+
|
||||
#
|
||||
# Key:
|
||||
# digit = next state
|
||||
# s = current item done, save it
|
||||
# + = Add current line to buffer
|
||||
# - = no-op
|
||||
#
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 2:
|
||||
sys.stderr.write("Single argument must be the input file name\n")
|
||||
sys.exit(1)
|
||||
|
||||
fname = sys.argv[1]
|
||||
state = 0 # Current FSM State
|
||||
buffer = [] # Work are in which we build an item
|
||||
lnum = 0
|
||||
key = ''
|
||||
|
||||
with open(fname,"r") as infile:
|
||||
for line in infile:
|
||||
lnum += 1
|
||||
line = fixSpecialCases(line.rstrip())
|
||||
|
||||
if not validate(line):
|
||||
sys.stderr.write(f"Unbalanced angle-brackets in line {lnum}\n")
|
||||
else:
|
||||
token,k,line = examine(line)
|
||||
|
||||
if state == 0:
|
||||
if token==1 or token==2:
|
||||
buffer = [line]
|
||||
if token==2: key = k
|
||||
state = token
|
||||
else:
|
||||
if token < 3:
|
||||
save(state,key,buffer)
|
||||
buffer = [line]
|
||||
if token==2: key = k
|
||||
state = token
|
||||
else:
|
||||
buffer.append(line)
|
||||
|
||||
# On EOF, close out the last unclosed item if there is one
|
||||
if state > 0:
|
||||
save(state,key,buffer)
|
||||
|
||||
# Write out the HTML
|
||||
print(prefix)
|
||||
|
||||
print('<h1 id="_commands">Commands</h1>')
|
||||
for v in sorted(commands):
|
||||
print(resolve(v))
|
||||
|
||||
print('<h1 id="_parameters">Parameters</h1>')
|
||||
for k,v in productions.items():
|
||||
print(resolve(v,k))
|
||||
|
||||
print(suffix)
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>$(opensslIncludeDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalLibraryDirectories>$(opensslOutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>ws2_32.lib;libcrypto.lib;libssl.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<PropertyGroup>
|
||||
<_DLLSuffix>-3</_DLLSuffix>
|
||||
<_DLLSuffix Condition="$(Platform) == 'ARM'">$(_DLLSuffix)-arm</_DLLSuffix>
|
||||
<_DLLSuffix Condition="$(Platform) == 'ARM64'">$(_DLLSuffix)-arm64</_DLLSuffix>
|
||||
<_DLLSuffix Condition="$(Platform) == 'x64'">$(_DLLSuffix)-x64</_DLLSuffix>
|
||||
</PropertyGroup>
|
||||
<!-- GAM Static Build: Disable missing DLL/PDB copy
|
||||
<ItemGroup>
|
||||
<_SSLDLL Include="$(opensslOutDir)\libcrypto$(_DLLSuffix).dll" />
|
||||
<_SSLDLL Include="$(opensslOutDir)\libcrypto$(_DLLSuffix).pdb" />
|
||||
<_SSLDLL Include="$(opensslOutDir)\libssl$(_DLLSuffix).dll" />
|
||||
<_SSLDLL Include="$(opensslOutDir)\libssl$(_DLLSuffix).pdb" />
|
||||
</ItemGroup>
|
||||
-->
|
||||
<Target Name="_CopySSLDLL" Inputs="@(_SSLDLL)" Outputs="@(_SSLDLL->'$(OutDir)%(Filename)%(Extension)')" AfterTargets="Build">
|
||||
<Copy SourceFiles="@(_SSLDLL)" DestinationFolder="$(OutDir)" />
|
||||
</Target>
|
||||
<Target Name="_CleanSSLDLL" BeforeTargets="Clean">
|
||||
<Delete Files="@(_SSLDLL->'$(OutDir)%(Filename)%(Extension)')" TreatErrorsAsWarnings="true" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -1,67 +0,0 @@
|
||||
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
|
||||
index 1235eff72f7..f68e34b7560 100644
|
||||
--- a/Modules/_ssl.c
|
||||
+++ b/Modules/_ssl.c
|
||||
@@ -134,6 +134,17 @@ static void _PySSLFixErrno(void) {
|
||||
#error Unsupported OpenSSL version
|
||||
#endif
|
||||
|
||||
+#if (OPENSSL_VERSION_NUMBER >= 0x40000000L)
|
||||
+# define OPENSSL_NO_SSL3
|
||||
+# define OPENSSL_NO_TLS1
|
||||
+# define OPENSSL_NO_TLS1_1
|
||||
+# define OPENSSL_NO_TLS1_2
|
||||
+# define OPENSSL_NO_SSL3_METHOD
|
||||
+# define OPENSSL_NO_TLS1_METHOD
|
||||
+# define OPENSSL_NO_TLS1_1_METHOD
|
||||
+# define OPENSSL_NO_TLS1_2_METHOD
|
||||
+#endif
|
||||
+
|
||||
/* OpenSSL API 1.1.0+ does not include version methods */
|
||||
#ifndef OPENSSL_NO_SSL3_METHOD
|
||||
extern const SSL_METHOD *SSLv3_method(void);
|
||||
@@ -1133,7 +1144,7 @@ _asn1obj2py(_sslmodulestate *state, const ASN1_OBJECT *name, int no_name)
|
||||
|
||||
static PyObject *
|
||||
_create_tuple_for_attribute(_sslmodulestate *state,
|
||||
- ASN1_OBJECT *name, ASN1_STRING *value)
|
||||
+ const ASN1_OBJECT *name, const ASN1_STRING *value)
|
||||
{
|
||||
Py_ssize_t buflen;
|
||||
PyObject *pyattr;
|
||||
@@ -1162,16 +1173,16 @@ _create_tuple_for_attribute(_sslmodulestate *state,
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
-_create_tuple_for_X509_NAME (_sslmodulestate *state, X509_NAME *xname)
|
||||
+_create_tuple_for_X509_NAME (_sslmodulestate *state, const X509_NAME *xname)
|
||||
{
|
||||
PyObject *dn = NULL; /* tuple which represents the "distinguished name" */
|
||||
PyObject *rdn = NULL; /* tuple to hold a "relative distinguished name" */
|
||||
PyObject *rdnt;
|
||||
PyObject *attr = NULL; /* tuple to hold an attribute */
|
||||
int entry_count = X509_NAME_entry_count(xname);
|
||||
- X509_NAME_ENTRY *entry;
|
||||
- ASN1_OBJECT *name;
|
||||
- ASN1_STRING *value;
|
||||
+ const X509_NAME_ENTRY *entry;
|
||||
+ const ASN1_OBJECT *name;
|
||||
+ const ASN1_STRING *value;
|
||||
int index_counter;
|
||||
int rdn_level = -1;
|
||||
int retcode;
|
||||
@@ -6506,9 +6517,15 @@ sslmodule_init_constants(PyObject *m)
|
||||
ADD_INT_CONST("PROTOCOL_TLS", PY_SSL_VERSION_TLS);
|
||||
ADD_INT_CONST("PROTOCOL_TLS_CLIENT", PY_SSL_VERSION_TLS_CLIENT);
|
||||
ADD_INT_CONST("PROTOCOL_TLS_SERVER", PY_SSL_VERSION_TLS_SERVER);
|
||||
+#ifndef OPENSSL_NO_TLS1
|
||||
ADD_INT_CONST("PROTOCOL_TLSv1", PY_SSL_VERSION_TLS1);
|
||||
+#endif
|
||||
+#ifndef OPENSSL_NO_TLS1_1
|
||||
ADD_INT_CONST("PROTOCOL_TLSv1_1", PY_SSL_VERSION_TLS1_1);
|
||||
+#endif
|
||||
+#ifndef OPENSSL_NO_TLS1_2
|
||||
ADD_INT_CONST("PROTOCOL_TLSv1_2", PY_SSL_VERSION_TLS1_2);
|
||||
+#endif
|
||||
|
||||
#define ADD_OPTION(NAME, VALUE) if (sslmodule_add_option(m, NAME, (VALUE)) < 0) return -1
|
||||
@@ -1,204 +0,0 @@
|
||||
// Node.js script to launch Simply Sign Desktop app and log a user in
|
||||
// using native Windows keystrokes and screenshot-desktop for reliable CI imaging.
|
||||
|
||||
import { execSync, spawn } from 'child_process';
|
||||
import { TOTP } from 'totp-generator';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// Native PowerShell Keystroke Sender
|
||||
function sendKeys(keys) {
|
||||
const script = `$wshell = New-Object -ComObject wscript.shell; $wshell.SendKeys('${keys}')`;
|
||||
execSync(`powershell -Command "${script}"`);
|
||||
}
|
||||
|
||||
// Native PowerShell Desktop Clear
|
||||
function minimizeAllWindows() {
|
||||
console.log('Minimizing all rogue background windows...');
|
||||
const script = `$shell = New-Object -ComObject "Shell.Application"; $shell.MinimizeAll()`;
|
||||
try {
|
||||
execSync(`powershell -Command "${script}"`);
|
||||
} catch (err) {
|
||||
console.log('Minimize command failed silently.');
|
||||
}
|
||||
}
|
||||
|
||||
async function takeScreenshot(filename) {
|
||||
const workspace = process.env.GITHUB_WORKSPACE || process.cwd();
|
||||
const fullPath = path.join(workspace, filename);
|
||||
|
||||
// Create a temporary script file path
|
||||
const scriptPath = path.join(workspace, `screenshot_${Date.now()}.ps1`);
|
||||
|
||||
const psScript = `
|
||||
Add-Type -AssemblyName System.Windows.Forms;
|
||||
Add-Type -AssemblyName System.Drawing;
|
||||
$Screen = [System.Windows.Forms.SystemInformation]::VirtualScreen;
|
||||
|
||||
if ($Screen.Width -eq 0 -or $Screen.Height -eq 0) {
|
||||
Write-Error "Screen dimensions are 0x0. Desktop not fully initialized.";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
$bitmap = New-Object System.Drawing.Bitmap $Screen.Width, $Screen.Height;
|
||||
$graphic = [System.Drawing.Graphics]::FromImage($bitmap);
|
||||
$graphic.CopyFromScreen($Screen.Left, $Screen.Top, 0, 0, $bitmap.Size);
|
||||
|
||||
# Save the file (using single quotes safely now)
|
||||
$bitmap.Save('${fullPath}');
|
||||
Write-Output "Wrote ${fullPath}";
|
||||
|
||||
# Specify ItemType to prevent older PS versions from prompting interactively
|
||||
New-Item -Path "${fullPath}.written" -ItemType File | Out-Null;
|
||||
`;
|
||||
|
||||
try {
|
||||
// 1. Write the script to disk
|
||||
fs.writeFileSync(scriptPath, psScript);
|
||||
|
||||
// 2. Execute the file directly, piping stdout/stderr to the Node console
|
||||
execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${scriptPath}"`, {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
|
||||
console.log(`Saved screenshot: ${fullPath}`);
|
||||
} catch (err) {
|
||||
console.error(`Failed to save screenshot ${fullPath}:`, err.message);
|
||||
} finally {
|
||||
// 3. Clean up the temp file so it doesn't clutter your CI artifacts
|
||||
if (fs.existsSync(scriptPath)) {
|
||||
fs.unlinkSync(scriptPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fire and forget application launcher with logging
|
||||
function launchSSD() {
|
||||
const workspace = process.env.GITHUB_WORKSPACE || process.cwd();
|
||||
const outLogPath = path.join(workspace, 'ssd_out.log');
|
||||
const errLogPath = path.join(workspace, 'ssd_err.log');
|
||||
|
||||
// Open file descriptors for logging
|
||||
const out = fs.openSync(outLogPath, 'a');
|
||||
const err = fs.openSync(errLogPath, 'a');
|
||||
|
||||
console.log(`Launching SSD... Logging stdout to ${outLogPath} and stderr to ${errLogPath}`);
|
||||
|
||||
const child = spawn('C:\\Program Files\\Certum\\SimplySign Desktop\\SimplySignDesktop.exe', [], {
|
||||
detached: true,
|
||||
// stdio array: [stdin, stdout, stderr]
|
||||
// We ignore stdin, and pipe stdout/stderr to our files
|
||||
stdio: ['ignore', out, err]
|
||||
});
|
||||
|
||||
// Catch immediate errors (e.g., file not found, permission denied)
|
||||
child.on('error', (error) => {
|
||||
console.error('Failed to spawn SimplySign Desktop:', error.message);
|
||||
});
|
||||
|
||||
// Unreference the child so the parent script can exit
|
||||
child.unref();
|
||||
}
|
||||
|
||||
async function runSSD() {
|
||||
const runner_arch = process.env.RUNNER_ARCH;
|
||||
if ( runner_arch === "ARM64" ) {
|
||||
console.log('Running on ARM64. Tabbing through OOBE...');
|
||||
await sleep(3000);
|
||||
await takeScreenshot('oob1.png');
|
||||
|
||||
// Page 1: Tab through the toggles to reach the "Next" button
|
||||
for (let i = 0; i < 7; i++) {
|
||||
sendKeys('{TAB}');
|
||||
await sleep(200);
|
||||
}
|
||||
sendKeys('{ENTER}');
|
||||
console.log('Clicked Next');
|
||||
|
||||
await sleep(3000);
|
||||
await takeScreenshot('ooob2.png');
|
||||
|
||||
// Page 2: Tab through the remaining toggles to reach the "Accept" button
|
||||
for (let i = 0; i < 7; i++) {
|
||||
sendKeys('{TAB}');
|
||||
await sleep(200);
|
||||
}
|
||||
sendKeys('{ENTER}');
|
||||
console.log('Clicked Accept');
|
||||
|
||||
await sleep(3000);
|
||||
await takeScreenshot('oob3.png');
|
||||
|
||||
sendKeys('{ESC}');
|
||||
console.log('Escaped start menu');
|
||||
|
||||
await sleep(3000);
|
||||
await takeScreenshot('oob4.png');
|
||||
|
||||
} else {
|
||||
console.log('NOT running on ARM64');
|
||||
}
|
||||
// Re-execute SSD to open login dialog
|
||||
launchSSD();
|
||||
await sleep(3000);
|
||||
await takeScreenshot('008.png');
|
||||
launchSSD();
|
||||
await sleep(3000);
|
||||
await takeScreenshot('009.png');
|
||||
|
||||
// 2. Login Flow
|
||||
console.log('Typing credentials...');
|
||||
|
||||
// Type Email
|
||||
sendKeys('jay0lee@gmail.com');
|
||||
await sleep(500);
|
||||
await takeScreenshot('010.png');
|
||||
|
||||
// Tab to next field
|
||||
sendKeys('{TAB}');
|
||||
await sleep(500);
|
||||
|
||||
// Generate and type TOTP
|
||||
console.log(`Our secret is ${process.env.TOTP_SECRET.length} characters.`);
|
||||
const { otp } = await TOTP.generate(process.env.TOTP_SECRET, {algorithm: 'SHA-256'});
|
||||
console.log(`Our token is ${otp.length} characters.`);
|
||||
|
||||
sendKeys(otp);
|
||||
await sleep(500);
|
||||
await takeScreenshot('011.png');
|
||||
|
||||
// Submit
|
||||
sendKeys('{ENTER}');
|
||||
console.log('Login sequence complete.');
|
||||
|
||||
// Screenshot cascade to monitor the window closing
|
||||
await takeScreenshot('012.png');
|
||||
await sleep(500);
|
||||
await takeScreenshot('013.png');
|
||||
await sleep(500);
|
||||
await takeScreenshot('014.png');
|
||||
await sleep(500);
|
||||
|
||||
|
||||
console.log('Exiting script, leaving SimplySign running in background.');
|
||||
|
||||
// Verification block to list all PNGs in the workspace
|
||||
console.log('\n--- Screenshot Verification ---');
|
||||
const workspace = process.env.GITHUB_WORKSPACE || process.cwd();
|
||||
try {
|
||||
const files = fs.readdirSync(workspace);
|
||||
const pngFiles = files.filter(f => f.endsWith('.png'));
|
||||
console.log(`Target Directory: ${workspace}`);
|
||||
console.log(`Found ${pngFiles.length} .png files:`);
|
||||
pngFiles.forEach(f => console.log(` - ${f}`));
|
||||
} catch (err) {
|
||||
console.error(`Error reading directory ${workspace}:`, err.message);
|
||||
}
|
||||
console.log('-------------------------------\n');
|
||||
}
|
||||
|
||||
runSSD();
|
||||
@@ -1,42 +0,0 @@
|
||||
# UTF-8
|
||||
#
|
||||
# For more details about fixed file info 'ffi' see:
|
||||
# http://msdn.microsoft.com/en-us/library/ms646997.aspx
|
||||
VSVersionInfo(
|
||||
ffi=FixedFileInfo(
|
||||
# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
|
||||
# Set not needed items to zero 0.
|
||||
filevers={VERSION_TUPLE},
|
||||
prodvers={VERSION_TUPLE},
|
||||
# Contains a bitmask that specifies the valid bits 'flags'r
|
||||
mask=0x3f,
|
||||
# Contains a bitmask that specifies the Boolean attributes of the file.
|
||||
flags=0x0,
|
||||
# The operating system for which this file was designed.
|
||||
# 0x4 - NT and there is no need to change it.
|
||||
OS=0x4,
|
||||
# The general type of file.
|
||||
# 0x1 - the file is an application.
|
||||
fileType=0x2,
|
||||
# The function of the file.
|
||||
# 0x0 - the function is not defined for this fileType
|
||||
subtype=0x0,
|
||||
# Creation date and time stamp.
|
||||
date=(0, 0)
|
||||
),
|
||||
kids=[
|
||||
StringFileInfo(
|
||||
[
|
||||
StringTable(
|
||||
'040904b0',
|
||||
[StringStruct('CompanyName', 'GAM-team'),
|
||||
StringStruct('FileDescription', 'CLI for Google Workspace admins'),
|
||||
StringStruct('FileVersion', '{VERSION}'),
|
||||
StringStruct('LegalCopyright', 'Copyright (c) 2024 GAM team'),
|
||||
StringStruct('OriginalFilename', 'gam.exe'),
|
||||
StringStruct('ProductName', 'GAM'),
|
||||
StringStruct('ProductVersion', '{VERSION}')])
|
||||
]),
|
||||
VarFileInfo([VarStruct('Translation', [1033, 1200])])
|
||||
]
|
||||
)
|
||||
@@ -1,5 +0,0 @@
|
||||
# Scratch Me Wiki
|
||||
editing these files shouldn't trigger a regular GitHub Action build.
|
||||
|
||||
# Counter
|
||||
00010
|
||||
@@ -1,30 +0,0 @@
|
||||
# Addresses
|
||||
- [API documentation](#api-documentation)
|
||||
- [Display addresses](#display-addresses)
|
||||
|
||||
## API documentation
|
||||
* [Directory API - Domains](https://developers.google.com/admin-sdk/directory/reference/rest/v1/domains)
|
||||
* [Directory API - Groups](https://developers.google.com/admin-sdk/directory/reference/rest/v1/groups)
|
||||
* [Directory API - Resources Calendars](https://developers.google.com/admin-sdk/directory/reference/rest/v1/resources.calendars)
|
||||
* [Directory API - Users](https://developers.google.com/admin-sdk/directory/reference/rest/v1/users)
|
||||
|
||||
## Display addresses
|
||||
Produces a three column CSV file (headers Type, Email, Target) that displays all group and user primary
|
||||
email addresses and aliases; resource calendar addresses and domain names.
|
||||
|
||||
The types are:
|
||||
```
|
||||
DomainPrimary, DomainSecondary, DomainAlias
|
||||
Group, GroupAlias, GroupNEAlias
|
||||
Resource
|
||||
SuspendedUser, SuspendedUserAlias, SuspendedUserNEAlias
|
||||
User, UserAlias, UserNEAlias
|
||||
```
|
||||
'NE' is an abbreviation for NonEditable.
|
||||
```
|
||||
gam print addresses [todrive <ToDriveAttribute>*]
|
||||
[domain <DomainName>]
|
||||
```
|
||||
By default, groups and users in all domains in the account are selected; this options allows selection of subsets of groups and users:
|
||||
* `domain <DomainName>` - Limit groups and users to those in `<DomainName>`
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,111 +0,0 @@
|
||||
# Alert Center
|
||||
- [API documentation](#api-documentation)
|
||||
- [Query documentation](#query-documentation)
|
||||
- [Definitions](#definitions)
|
||||
- [Introduction](#introduction)
|
||||
- [Manage alerts](#manage-alerts)
|
||||
- [Display alerts](#display-alerts)
|
||||
- [Manage alert feedback](#manage-alert-feedback)
|
||||
- [Display alert feedback](#display-alert-feedback)
|
||||
- [Configuring settings](#configuring-settings)
|
||||
|
||||
## API documentation
|
||||
* [Alert Center API](https://developers.google.com/admin-sdk/alertcenter/reference/rest/)
|
||||
|
||||
## Query documentation
|
||||
* [Query Filters](https://developers.google.com/admin-sdk/alertcenter/guides/query-filters)
|
||||
* [Query Fields](https://developers.google.com/admin-sdk/alertcenter/reference/filter-fields)
|
||||
|
||||
## Definitions
|
||||
```
|
||||
<AlertID> ::= <String>
|
||||
<PubSubTopicName> ::= <String>
|
||||
<QueryAlert> ::= <String> See: https://developers.google.com/admin-sdk/alertcenter/guides/query-filters
|
||||
```
|
||||
## Introduction
|
||||
For an introduction, start here: https://support.google.com/a/answer/9105393
|
||||
|
||||
This API is in beta, most things seem to work although the filter queries don't all work, in particular those that
|
||||
select alertId and feedbackId.
|
||||
|
||||
To use these commands you must update your gam project and service account authorization.
|
||||
```
|
||||
gam update project
|
||||
gam user user@domain.com update serviceaccount
|
||||
```
|
||||
## Manage alerts
|
||||
```
|
||||
gam delete alert <AlertID>
|
||||
gam undelete alert <AlertID>
|
||||
```
|
||||
## Display alerts
|
||||
```
|
||||
gam info alert <AlertID> [formatjson]
|
||||
gam show alerts [filter <QueryAlert>] [orderby createtime [ascending|descending]]
|
||||
[formatjson]
|
||||
```
|
||||
By default, Gam displays the information as an indented list of keys and values.
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
```
|
||||
gam print alerts [todrive <ToDriveAttributes>*] [filter <QueryAlert>] [orderby createtime [ascending|descending]]
|
||||
[formatjson [quotechar <Character>]]
|
||||
```
|
||||
By default, Gam displays the information as columns of fields; the following option causes the output to be in JSON format,
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
|
||||
By default, when writing CSV files, Gam uses a quote character of double quote `"`. The quote character is used to enclose columns that contain
|
||||
the quote character itself, the column delimiter (comma by default) and new-line characters. Any quote characters within the column are doubled.
|
||||
When using the `formatjson` option, double quotes are used extensively in the data resulting in hard to read/process output.
|
||||
The `quotechar <Character>` option allows you to choose an alternate quote character, single quote for instance, that makes for readable/processable output.
|
||||
`quotechar` defaults to `gam.cfg/csv_output_quote_char`. When uploading CSV files to Google, double quote `"` should be used.
|
||||
|
||||
### Eliminate unwanted fields
|
||||
You can use [CSV Print Filtering](CSV-Print-Filtering) to reduce the amount of output.
|
||||
This command will drop all of the data.messages columns.
|
||||
```
|
||||
gam config csv_output_header_drop_filter "^data.messages" redirect csv alerts.csv print alerts
|
||||
```
|
||||
|
||||
## Manage alert feedback
|
||||
```
|
||||
gam create alertfeedback <AlertID> not_useful|somewhat_useful|very_useful
|
||||
```
|
||||
## Display alert feedback
|
||||
```
|
||||
gam show alertfeedback [alert <AlertID>] [filter <QueryAlert>] [orderby createtime [ascending|descending]]
|
||||
[formatjson]
|
||||
```
|
||||
By default, Gam displays feedback for all alerts.
|
||||
* `alert <AlertID>` - Display feedback for the selected alert
|
||||
* `filter <QueryAlert>` - Display feebback for the filtered alerts
|
||||
|
||||
By default, Gam displays the information as an indented list of keys and values.
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
```
|
||||
gam print alertfeedback [todrive <ToDriveAttributes>*] [alert <AlertID>] [filter <QueryAlert>] [orderby createtime [ascending|descending]]
|
||||
[formatjson [quotechar <Character>]]
|
||||
```
|
||||
By default, Gam displays feedback for all alerts.
|
||||
* `alert <AlertID>` - Display feedback for the selected alert
|
||||
* `filter <QueryAlert>` - Display feebback for the filtered alerts
|
||||
|
||||
By default, Gam displays the information as columns of fields; the following option causes the output to be in JSON format,
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
|
||||
By default, when writing CSV files, Gam uses a quote character of double quote `"`. The quote character is used to enclose columns that contain
|
||||
the quote character itself, the column delimiter (comma by default) and new-line characters. Any quote characters within the column are doubled.
|
||||
When using the `formatjson` option, double quotes are used extensively in the data resulting in hard to read/process output.
|
||||
The `quotechar <Character>` option allows you to choose an alternate quote character, single quote for instance, that makes for readable/processable output.
|
||||
`quotechar` defaults to `gam.cfg/csv_output_quote_char`. When uploading CSV files to Google, double quote `"` should be used.
|
||||
|
||||
## Configuring settings
|
||||
|
||||
Alert Center can be configured to send notifications to a Google Cloud Pub/Sub topic, but it first requires configuration.
|
||||
* See https://developers.google.com/workspace/admin/alertcenter/guides/notifications for information.
|
||||
|
||||
Gam can be used to display or modify the settings:
|
||||
```
|
||||
gam show alertsettings
|
||||
gam update alertsettings <PubSubTopicName>
|
||||
gam clear alertsettings
|
||||
```
|
||||
201
wiki/Aliases.md
201
wiki/Aliases.md
@@ -1,201 +0,0 @@
|
||||
# Aliases
|
||||
- [API documentation](#api-documentation)
|
||||
- [Query documentation](#query-documentation)
|
||||
- [Python Regular Expressions](Python-Regular-Expressions) Match function
|
||||
- [Definitions](#definitions)
|
||||
- [Create an alias for a target](#create-an-alias-for-a-target)
|
||||
- [Update an alias to point to a new target](#update-an-alias-to-point-to-a-new-target)
|
||||
- [Delete an alias regardless of the target](#delete-an-alias-regardless-of-the-target)
|
||||
- [Remove aliases from a specified target](#remove-aliases-from-a-specified-target)
|
||||
- [Delete all of a user's aliases](#delete-all-of-a-users-aliases)
|
||||
- [Display aliases](#display-aliases)
|
||||
- [Bulk delete aliases](#bulk-delete-aliases)
|
||||
- [Bulk reassign aliases](#bulk-reassign-aliases)
|
||||
- [Determine if an address is a user, user alias, group or group alias](#determine-if-an-address-is-a-user-user-alias-group-or-group-alias)
|
||||
|
||||
## API documentation
|
||||
* [Directory API - User Aliases](https://developers.google.com/admin-sdk/directory/reference/rest/v1/users.aliases)
|
||||
* [Directory API - Group Aliases](https://developers.google.com/admin-sdk/directory/reference/rest/v1/groups.aliases)
|
||||
|
||||
## Query documentation
|
||||
* [Search Users](https://developers.google.com/admin-sdk/directory/v1/guides/search-users)
|
||||
|
||||
## Definitions
|
||||
See [Collections of Items](Collections-of-Items)
|
||||
```
|
||||
<DomainName> ::= <String>(.<String>)+
|
||||
<DomainNameList> ::= "<DomainName>(,<DomainName>)*"
|
||||
<DomainNameEntity> ::=
|
||||
<DomainNameList> | <FileSelector> | <CSVFileSelector>
|
||||
<EmailAddress> ::= <String>@<DomainName>
|
||||
<EmailAddressList> ::= "<EmailAddress>(,<EmailAddress>)*"
|
||||
<EmailAddressEntity> ::= <EmailAddressList> | <FileSelector> | <CSVkmdSelector> | <CSVDataSelector>
|
||||
See: https://github.com/GAM-team/GAM/wiki/Collections-of-Items
|
||||
<UniqueID> ::= id:<String>
|
||||
|
||||
<RegularExpression> ::= <String>
|
||||
See: https://docs.python.org/3/library/re.html
|
||||
<REMatchPattern> ::= <RegularExpression>
|
||||
<RESearchPattern> ::= <RegularExpression>
|
||||
<RESubstitution> ::= <String>>
|
||||
```
|
||||
## Create an alias for a target
|
||||
```
|
||||
gam create alias|aliases <EmailAddressEntity> user|group|target <UniqueID>|<EmailAddress>
|
||||
[verifynotinvitable]
|
||||
```
|
||||
`<EmailAddressEntity>` are the aliases, `<EmailAddress>` is the target.
|
||||
|
||||
The `verifynotinvitable` option causes GAM to verify that the alias email address being created is not that of an unmanaged account;
|
||||
if it is, the command is not performed.
|
||||
|
||||
### Example
|
||||
|
||||
To allow Robert to also receive mail as Bob:
|
||||
|
||||
```
|
||||
gam create alias bob[@yourdomain.com] user robert[@yourdomain.com]
|
||||
```
|
||||
|
||||
## Update an alias to point to a new target
|
||||
The existing alias is deleted and a new alias is created.
|
||||
```
|
||||
gam update alias|aliases <EmailAddressEntity> user|group|target <UniqueID>|<EmailAddress>
|
||||
[notargetverify] [waitafterdelete <Integer>]
|
||||
```
|
||||
`<EmailAddressEntity>` are the aliases, `<EmailAddress>` is the target.
|
||||
|
||||
By default, GAM makes additional API calls to verify that the target email address exists before updating the alias;
|
||||
if you know that the target exists, you can suppress the verification with `notargetverify.
|
||||
|
||||
GAM updates an alias to point to a new target by deleting the alias and then recreates the alias pointing to the new target.
|
||||
Unfortunately, if these commands are executed back-to-back; Google generates the `Update Failed: Duplicate` error.
|
||||
Now, GAM waits 2 seconds between the delete and the insert which seems to eliminate the problem. If the problem persists,
|
||||
use the option `waitafterdelete <Integer>` to increase the wait time to a maximum of 10 seconds.
|
||||
|
||||
## Delete an alias regardless of the target
|
||||
```
|
||||
gam delete alias|aliases [user|group|target] <EmailAddressEntity>
|
||||
```
|
||||
`<EmailAddressEntity>` are the aliases.
|
||||
|
||||
## Remove aliases from a specified target
|
||||
```
|
||||
gam remove alias|aliases <EmailAddress> user|group <EmailAddressEntity>
|
||||
```
|
||||
`<EmailAddress>` is the target, `<EmailAddressEntity>` are the aliases.
|
||||
|
||||
## Delete all of a user's aliases
|
||||
```
|
||||
gam <UserTypeEntity> delete aliases
|
||||
```
|
||||
|
||||
## Display aliases
|
||||
Display a specific alias.
|
||||
```
|
||||
gam info alias|aliases <EmailAddressEntity>
|
||||
```
|
||||
|
||||
Display selected aliases.
|
||||
```
|
||||
gam print aliases [todrive <ToDriveAttribute>*]
|
||||
([domain|domains <DomainNameEntity>] [(query <QueryUser>)|(queries <QueryUserList>)]
|
||||
[limittoou <OrgUnitItem>])
|
||||
[user|users <EmailAddressList>] [group|groups <EmailAddressList>]
|
||||
[select <UserTypeEntity>]
|
||||
[issuspended <Boolean>] [isarchived <Boolean>] [aliasmatchpattern <REMatchPattern>]
|
||||
[shownoneditable] [nogroups] [nousers]
|
||||
[onerowpertarget] [delimiter <Character>]
|
||||
[suppressnoaliasrows]
|
||||
(addcsvdata <FieldName> <String>)*
|
||||
```
|
||||
By default, group and user aliases in all domains in the account are selected; these options allow selection of subsets of aliases:
|
||||
* `domain|domains <DomainNameEntity>` - Limit aliases to those in the domains specified by `<DomainNameEntity>`
|
||||
* You can predefine this list with the `print_agu_domains` variable in `gam.cfg`.
|
||||
* `(query <QueryUser>)|(queries <QueryUserList>)` - Print aliases for users/groups that match a query; each query is run against each domain
|
||||
* `limittoou <OrgUnitItem>` - Print aliases for users in the specified `<OrgUnitItem>`
|
||||
* `user|users <EmailAddressList>` - Print aliases for users in `<EmailAddressList`
|
||||
* `select <UserTypeEntity>` - Print aliases for users in `<UserTypeEntity>`
|
||||
* `group|groups <EmailAddressList>` - Print aliases for groups in `<EmailAddressList`
|
||||
* `issuspended <Boolean>` - Limit users based on their status
|
||||
* `isarchived <Boolean>` - Limit users based on their status
|
||||
* `aliasmatchpattern <REMatchPattern>` - Print aliases that match a pattern
|
||||
* `nogroups` - Print only user aliases
|
||||
* `nousers` - Print only group aliases
|
||||
|
||||
By default, the CSV output has three columns: `Alias,Target,TargetType`; if a target
|
||||
has multiple aliases, there will be multiple rows, one per alias.
|
||||
|
||||
Use `shownoneditable` to list non-editable alias email addresses; these are typically outside of the account's primary domain or subdomains.
|
||||
This adds the column `NonEditableAlias`.
|
||||
|
||||
Specifying `onerowpertarget` changes the three columns to: `Target,TargetType,Aliases`; all aliases for the target are listed in the
|
||||
`Aliases` column. If `shownoneditable` is specified, there will be a fourth column `NonEditableAliases` with a list of non-editable aliases.
|
||||
|
||||
By default, the aliases in a list are separated by the `csv_output_field_delimiter' from `gam.cfg`.
|
||||
* `delimiter <Character>` - Separate aliases in a list with `<Character>`
|
||||
|
||||
Specifying both `onerowpertarget` and `suppressnoaliasrows` causes GAM to not display any targets that have no aliases.
|
||||
|
||||
Add additional columns of data from the command line to the output
|
||||
* `addcsvdata <FieldName> <String>`
|
||||
|
||||
When multiple domains are specified and a query/queries are specified, an API call is made for each domain/query combination.
|
||||
```
|
||||
$ gam print aliases domains school.org,students.school.org queries "'email:admin*','email:test*'"
|
||||
Getting all Users that match query (domain=school.org, query="email:admin*"), may take some time on a large Google Workspace Account...
|
||||
Got 3 Users: admin@school.org - admindirector@school.org
|
||||
Getting all Users that match query (domain=school.org, query="email:test*"), may take some time on a large Google Workspace Account...
|
||||
Got 20 Users: testusera@school.org - testuserx@school.org
|
||||
Getting all Users that match query (domain=students.school.org, query="email:admin*"), may take some time on a large Google Workspace Account...
|
||||
Got 1 User: admin@students.school.org - admin@students.school.org
|
||||
Getting all Users that match query (domain=students.school.org, query="email:test*"), may take some time on a large Google Workspace Account...
|
||||
Got 1 User: testuser1@students.school.org - testuser1@students.school.org
|
||||
Alias,Target,TargetType
|
||||
...
|
||||
```
|
||||
|
||||
## Bulk delete aliases
|
||||
You can bulk delete aliases as follows; use `(query <QueryUser>)|(queries <QueryUserList>)` and
|
||||
`aliasmatchpattern <REMatchPattern>` as desired.
|
||||
```
|
||||
gam redirect csv ./OldDomainAliases.csv print aliases aliasmatchpattern ".*@olddomain.com" onerowpertarget suppressnoaliasrows
|
||||
gam redirect stdout ./DeleteAliases.txt multiprocess redirect stderr stdout csv ./OldDomainAliases.csv gam remove aliases "~Target" "~TargetType" "~Aliases"
|
||||
```
|
||||
|
||||
## Bulk reassign aliases
|
||||
You can bulk reassign aliases as follows. Make a CSV file ReassignAliases.csv with two columns: OldTarget,NewTarget.
|
||||
From this CSV file, all of the aliases for the users in the OldTarget column will be listed with an additional column showing the NewTarget.
|
||||
```
|
||||
gam redirect stdout ./GetAliases.txt multiprocess redirect stderr stdout redirect csv ./ReassignAliases.csv gam print aliases user "~OldTarget" addcsvdata NewTarget "~NewTarget"
|
||||
```
|
||||
If an OldTarget's aliases are to be reassigned to more than the one NewTarget, edit ReassignAliases.csv and make changes as required.
|
||||
```
|
||||
gam redirect stdout ./ReassignAliases.txt multiprocess redirect stderr stdout csv ReassignAliases.csv gam update alias "~Alias" user "~NewTarget"
|
||||
```
|
||||
|
||||
## Determine if an address is a user, user alias, group or group alias
|
||||
```
|
||||
gam whatis <EmailItem> [noinfo] [noinvitablecheck]
|
||||
```
|
||||
The first line of output is: `<TypeOfEmailItem>: <EmailItem>`
|
||||
|
||||
There is additional output based on `<TypeOfEmailItem>`:
|
||||
* User - `gam info user <EmailItem>`
|
||||
* Group - `gam info group <EmailItem>`
|
||||
* User Alias - `gam info alias <EmailItem>`
|
||||
* Group Alias - `gam info alias <EmailItem>`
|
||||
* User Invitation - `gam info userinvitation <EmailItem>`
|
||||
|
||||
The `noinfo` argument suppresses the additional output.
|
||||
|
||||
The `noinvitablecheck` argument suppresses the user invitation check
|
||||
to avoid exceeding quota limits when checking a large number of addresses.
|
||||
|
||||
The return code is set based on `<TypeOfEmailItem>`:
|
||||
* User - 20
|
||||
* User Alias - 21
|
||||
* Group - 22
|
||||
* Group Alias - 23
|
||||
* User Invitation - 24
|
||||
* Unknown - 59
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,31 +0,0 @@
|
||||
# Syntax
|
||||
|
||||
## BNF Syntax
|
||||
This Wiki describes the GAM7 command line syntax in modified BNF.
|
||||
* https://en.wikipedia.org/wiki/Backus-Naur_Form
|
||||
|
||||
Skip the History section and start reading at Introduction.
|
||||
|
||||
Items on the command line are space separated, when an actual space character is required, it will be indicated by ```<Space>```.
|
||||
If an item contains spaces, it should be surrounded by ".
|
||||
|
||||
Metasyntactic symbols
|
||||
```
|
||||
[] optional item
|
||||
() group items
|
||||
* item may appear zero or more times
|
||||
+ item may appear one or more times
|
||||
| separates alternative items
|
||||
```
|
||||
## Items
|
||||
- [Basic](Basic-Items)
|
||||
- [Lists](List-Items)
|
||||
|
||||
## Collections
|
||||
- [ChromeOS Devices](Collections-of-ChromeOS-Devices)
|
||||
- [Users](Collections-of-Users)
|
||||
- [Items](Collections-of-Items)
|
||||
- [Verify Collections](List)
|
||||
|
||||
## Python Regular Expressions
|
||||
- [Python Regular Expressions](Python-Regular-Expressions)
|
||||
@@ -1,594 +0,0 @@
|
||||
# Basic Items
|
||||
- [Primitives](#primitives)
|
||||
- [Items built from primitives](#items-built-from-primitives)
|
||||
- [Named items](#named-items)
|
||||
- [List Items](List-Items)
|
||||
|
||||
## Primitives
|
||||
```
|
||||
<Character> ::= a single character
|
||||
<Digit> ::= 0|1|2|3|4|5|6|7|8|9
|
||||
<Number> ::= <Digit>+
|
||||
<Float> ::= <Digit>*.<Digit>+
|
||||
<Hex> ::= <Digit>|a|b|c|d|e|f|A|B|C|D|E|F
|
||||
<Space> ::= an actual space character
|
||||
<String> ::= a string of characters, surrounded by " if it contains spaces
|
||||
<FalseValues>= false|off|no|disabled|0
|
||||
<TrueValues> ::= true|on|yes|enabled|1
|
||||
|
||||
<BCP47LanguageCode> ::=
|
||||
ar-sa| # Arabic Saudi Arabia
|
||||
cs-cz| # Czech Czech Republic
|
||||
da-dk| # Danish Denmark
|
||||
de-de| # German Germany
|
||||
el-gr| # Modern Greek Greece
|
||||
en-au| # English Australia
|
||||
en-gb| # English United Kingdom
|
||||
en-ie| # English Ireland
|
||||
en-us| # English United States
|
||||
en-za| # English South Africa
|
||||
es-es| # Spanish Spain
|
||||
es-mx| # Spanish Mexico
|
||||
fi-fi| # Finnish Finland
|
||||
fr-ca| # French Canada
|
||||
fr-fr| # French France
|
||||
he-il| # Hebrew Israel
|
||||
hi-in| # Hindi India
|
||||
hu-hu| # Hungarian Hungary
|
||||
id-id| # Indonesian Indonesia
|
||||
it-it| # Italian Italy
|
||||
ja-jp| # Japanese Japan
|
||||
ko-kr| # Korean Republic of Korea
|
||||
nl-be| # Dutch Belgium
|
||||
nl-nl| # Dutch Netherlands
|
||||
no-no| # Norwegian Norway
|
||||
pl-pl| # Polish Poland
|
||||
pt-br| # Portuguese Brazil
|
||||
pt-pt| # Portuguese Portugal
|
||||
ro-ro| # Romanian Romania
|
||||
ru-ru| # Russian Russian Federation
|
||||
sk-sk| # Slovak Slovakia
|
||||
sv-se| # Swedish Sweden
|
||||
th-th| # Thai Thailand
|
||||
tr-tr| # Turkish Turkey
|
||||
zh-cn| # Chinese China
|
||||
zh-hk| # Chinese Hong Kong
|
||||
zh-tw # Chinese Taiwan
|
||||
<Charset> ::= ascii|latin1|mbcs|utf-8|utf-8-sig|utf-16|<String>
|
||||
<CalendarColorIndex> ::= <Number in range 1-24>
|
||||
<CalendarColorName> ::=
|
||||
amethyst|avocado|banana|basil|birch|blueberry|
|
||||
cherryblossom|citron|cobalt|cocoa|eucalyptus|flamingo|
|
||||
grape|graphite|lavender|mango|peacock|pistachio|
|
||||
pumpkin|radicchio|sage|tangerine|tomato|wisteria|
|
||||
<ColorHex> ::= "#<Hex><Hex><Hex><Hex><Hex><Hex>"
|
||||
<ColorNameGoogle> ::=
|
||||
asparagus|bluevelvet|bubblegum|cardinal|chocolateicecream|denim|desertsand|
|
||||
earthworm|macaroni|marsorange|mountaingray|mountaingrey|mouse|oldbrickred|
|
||||
pool|purpledino|purplerain|rainysky|seafoam|slimegreen|spearmint|
|
||||
toyeggplant|vernfern|wildstrawberries|yellowcab
|
||||
<ColorNameWeb> ::=
|
||||
aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|
|
||||
blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|
|
||||
cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|
|
||||
darkgrey|darkgreen|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|
|
||||
darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|
|
||||
darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|
|
||||
firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|
|
||||
gray|grey|green|greenyellow|honeydew|hotpink|indianred|indigo|ivory|khaki|
|
||||
lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|
|
||||
lightgoldenrodyellow|lightgray|lightgrey|lightgreen|lightpink|lightsalmon|
|
||||
lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|
|
||||
lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|
|
||||
mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|
|
||||
mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|
|
||||
navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|
|
||||
palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|
|
||||
peru|pink|plum|powderblue|purple|red|rosybrown|royalblue|saddlebrown|salmon|
|
||||
sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|
|
||||
slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|
|
||||
wheat|white|whitesmoke|yellow|yellowgreen
|
||||
<ColorName> ::= <ColorNameGoogle>|<ColorNameWeb>
|
||||
<ColorValue> ::= <ColorName>|<ColorHex>
|
||||
<DayOfWeek> ::= mon|tue|wed|thu|fri|sat|sun
|
||||
<EventColorIndex> ::= <Number in range 1-11>
|
||||
<EventColorName> ::=
|
||||
banana|basil|blueberry|flamingo|graphite|grape|
|
||||
lavender|peacock|sage|tangerine|tomato
|
||||
<FileFormat> ::=
|
||||
csv|doc|dot|docx|dotx|epub|html|jpeg|jpg|json|mht|odp|ods|odt|
|
||||
pdf|png|ppt|pot|potx|pptx|rtf|svg|tsv|txt|xls|xlt|xlsx|xltx|zip|
|
||||
ms|microsoft|openoffice|
|
||||
<LabelColorHex> ::=
|
||||
#000000|#076239|#0b804b|#149e60|#16a766|#1a764d|#1c4587|#285bac|
|
||||
#2a9c68|#3c78d8|#3dc789|#41236d|#434343|#43d692|#44b984|#4a86e8|
|
||||
#653e9b|#666666|#68dfa9|#6d9eeb|#822111|#83334c|#89d3b2|#8e63ce|
|
||||
#999999|#a0eac9|#a46a21|#a479e2|#a4c2f4|#aa8831|#ac2b16|#b65775|
|
||||
#b694e8|#b9e4d0|#c6f3de|#c9daf8|#cc3a21|#cccccc|#cf8933|#d0bcf1|
|
||||
#d5ae49|#e07798|#e4d7f5|#e66550|#eaa041|#efa093|#efefef|#f2c960|
|
||||
#f3f3f3|#f691b3|#f6c5be|#f7a7c0|#fad165|#fb4c2f|#fbc8d9|#fcda83|
|
||||
#fcdee8|#fce8b3|#fef1d1|#ffad47|#ffbc6b|#ffd6a2|#ffe6c7|#ffffff
|
||||
<LabelBackgroundColorHex> ::=
|
||||
#16a765|#2da2bb|#42d692|#4986e7|#98d7e4|#a2dcc1|
|
||||
#b3efd3|#b6cff5|#b99aff|#c2c2c2|#cca6ac|#e3d7ff|
|
||||
#e7e7e7|#ebdbde|#f2b2a8|#f691b2|#fb4c2f|#fbd3e0|
|
||||
#fbe983|#fdedc1|#ff7537|#ffad46|#ffc8af|#ffdeb5
|
||||
<LabelTextColorHex> ::=
|
||||
#04502e|#094228|#0b4f30|#0d3472|#0d3b44|#3d188e|
|
||||
#464646|#594c05|#662e37|#684e07|#711a36|#7a2e0b|
|
||||
#7a4706|#8a1c0a|#994a64|#ffffff
|
||||
<LanguageCode> ::=
|
||||
ach|af|ag|ak|am|ar|az|be|bem|bg|bn|br|bs|ca|chr|ckb|co|crs|cs|cy|da|de|
|
||||
ee|el|en|en-ca|en-gb|en-us|eo|es|es-419|et|eu|fa|fi|fil|fo|fr|fr-ca|fy|
|
||||
ga|gaa|gd|gl|gn|gu|ha|haw|he|hi|hr|ht|hu|hy|ia|id|ig|in|is|it|iw|ja|jw|
|
||||
ka|kg|kk|km|kn|ko|kri|ku|ky|la|lg|ln|lo|loz|lt|lua|lv|
|
||||
mfe|mg|mi|mk|ml|mn|mo|mr|ms|mt|my|ne|nl|nn|no|nso|ny|nyn|oc|om|or|
|
||||
pa|pcm|pl|ps|pt-br|pt-pt|qu|rm|rn|ro|ru|rw|
|
||||
sd|sh|si|sk|sl|sn|so|sq|sr|sr-me|st|su|sv|sw|
|
||||
ta|te|tg|th|ti|tk|tl|tn|to|tr|tt|tum|tw|
|
||||
ug|uk|ur|uz|vi|wo|xh|yi|yo|zh-cn|zh-hk|zh-tw|zu
|
||||
<Language> ::=
|
||||
<LanguageCode>[+|-]|
|
||||
<String>
|
||||
<Locale> ::=
|
||||
''| #Not defined
|
||||
ar-eg| #Arabic, Egypt
|
||||
az-az| #Azerbaijani, Azerbaijan
|
||||
be-by| #Belarusian, Belarus
|
||||
bg-bg| #Bulgarian, Bulgaria
|
||||
bn-in| #Bengali, India
|
||||
ca-es| #Catalan, Spain
|
||||
cs-cz| #Czech, Czech Republic
|
||||
cy-gb| #Welsh, United Kingdom
|
||||
da-dk| #Danish, Denmark
|
||||
de-ch| #German, Switzerland
|
||||
de-de| #German, Germany
|
||||
el-gr| #Greek, Greece
|
||||
en-au| #English, Australia
|
||||
en-ca| #English, Canada
|
||||
en-gb| #English, United Kingdom
|
||||
en-ie| #English, Ireland
|
||||
en-us| #English, U.S.A.
|
||||
es-ar| #Spanish, Argentina
|
||||
es-bo| #Spanish, Bolivia
|
||||
es-cl| #Spanish, Chile
|
||||
es-co| #Spanish, Colombia
|
||||
es-ec| #Spanish, Ecuador
|
||||
es-es| #Spanish, Spain
|
||||
es-mx| #Spanish, Mexico
|
||||
es-py| #Spanish, Paraguay
|
||||
es-uy| #Spanish, Uruguay
|
||||
es-ve| #Spanish, Venezuela
|
||||
fi-fi| #Finnish, Finland
|
||||
fil-ph| #Filipino, Philippines
|
||||
fr-ca| #French, Canada
|
||||
fr-fr| #French, France
|
||||
gu-in| #Gujarati, India
|
||||
hi-in| #Hindi, India
|
||||
hr-hr| #Croatian, Croatia
|
||||
hu-hu| #Hungarian, Hungary
|
||||
hy-am| #Armenian, Armenia
|
||||
in-id| #Indonesian, Indonesia
|
||||
it-it| #Italian, Italy
|
||||
iw-il| #Hebrew, Israel
|
||||
ja-jp| #Japanese, Japan
|
||||
ka-ge| #Georgian, Georgia
|
||||
kk-kz| #Kazakh, Kazakhstan
|
||||
kn-in| #Kannada, India
|
||||
ko-kr| #Korean, Korea
|
||||
lt-lt| #Lithuanian, Lithuania
|
||||
lv-lv| #Latvian, Latvia
|
||||
ml-in| #Malayalam, India
|
||||
mn-mn| #Mongolian, Mongolia
|
||||
mr-in| #Marathi, India
|
||||
my-mn| #Burmese, Myanmar
|
||||
nl-nl| #Dutch, Netherlands
|
||||
nn-no| #Nynorsk, Norway
|
||||
no-no| #Bokmal, Norway
|
||||
pa-in| #Punjabi, India
|
||||
pl-pl| #Polish, Poland
|
||||
pt-br| #Portuguese, Brazil
|
||||
pt-pt| #Portuguese, Portugal
|
||||
ro-ro| #Romanian, Romania
|
||||
ru-ru| #Russian, Russia
|
||||
sk-sk| #Slovak, Slovakia
|
||||
sl-si| #Slovenian, Slovenia
|
||||
sr-rs| #Serbian, Serbia
|
||||
sv-se| #Swedish, Sweden
|
||||
ta-in| #Tamil, India
|
||||
te-in| #Telugu, India
|
||||
th-th| #Thai, Thailand
|
||||
tr-tr| #Turkish, Turkey
|
||||
uk-ua| #Ukrainian, Ukraine
|
||||
vi-vn| #Vietnamese, Vietnam
|
||||
zh-cn| #Simplified Chinese, China
|
||||
zh-hk| #Traditional Chinese, Hong Kong SAR China
|
||||
zh-tw #Traditional Chinese, Taiwan
|
||||
<MimeTypeShortcut> ::=
|
||||
gdoc|gdocument|
|
||||
gdrawing|
|
||||
gfile|
|
||||
gfolder|gdirectory|
|
||||
gform|
|
||||
gfusion|
|
||||
gjam|
|
||||
gmap|
|
||||
gpresentation|
|
||||
gscript|
|
||||
gsheet|gspreadsheet|
|
||||
gshortcut|
|
||||
g3pshortcut|
|
||||
gsite|
|
||||
shortcut
|
||||
<MimeTypeName> ::= application|audio|font|image|message|model|multipart|text|video
|
||||
<MimeType> ::= <MimeTypeShortcut>|(<MimeTypeName>/<String>)
|
||||
```
|
||||
## Items built from primitives
|
||||
```
|
||||
<Boolean> ::= <TrueValues>|<FalseValues>
|
||||
<ByteCount> ::= <Number>[m|k|b]
|
||||
<CIDRnetmask> ::= <Number>.<Number>.<Number>.<Number>/<Number>
|
||||
<Year> ::= <Digit><Digit><Digit><Digit>
|
||||
<Month> ::= <Digit><Digit>
|
||||
<Day> ::= <Digit><Digit>
|
||||
<Hour> ::= <Digit><Digit>
|
||||
<Minute> ::= <Digit><Digit>
|
||||
<Second> ::= <Digit><Digit>
|
||||
<MilliSeconds> ::= <Digit><Digit><Digit>
|
||||
<Date> ::=
|
||||
<Year>-<Month>-<Day> |
|
||||
(+|-)<Number>(d|w|y) |
|
||||
never|
|
||||
today
|
||||
<DateTime> ::=
|
||||
<Year>-<Month>-<Day>(<Space>|T)<Hour>:<Minute> |
|
||||
(+|-)<Number>(m|h|d|w|y) |
|
||||
never|
|
||||
now|today
|
||||
<Time> ::=
|
||||
<Year>-<Month>-<Day>(<Space>|T)<Hour>:<Minute>:<Second>[.<MilliSeconds>](Z|(+|-(<Hour>:<Minute>))) |
|
||||
(+|-)<Number>(m|h|d|w|y) |
|
||||
never|
|
||||
now|today
|
||||
<RegularExpression> ::= <String>
|
||||
See: https://docs.python.org/3/library/re.html
|
||||
<REMatchPattern> ::= <RegularExpression>
|
||||
<RESearchPattern> ::= <RegularExpression>
|
||||
<RESubstitution> ::= <String>>
|
||||
<ProjectID> ::= <String>
|
||||
Must match this Python Regular Expression: [a-z][a-z0-9-]{4,28}[a-z0-9]
|
||||
<ServiceAccountName> ::= <String>
|
||||
Must match this Python Regular Expression: [a-z][a-z0-9-]{4,28}[a-z0-9]
|
||||
<SiteName> ::= [a-z,0-9,-]+
|
||||
<UniqueID> ::= id:<String>|uid:<String>
|
||||
```
|
||||
## Named items
|
||||
```
|
||||
<AccessToken> ::= <String>
|
||||
<AdminAssigneeType> ::= group|user|serviceaccount|unknown
|
||||
<AlertID> ::= <String>
|
||||
<APIScopeURL> ::= <String>
|
||||
<APPID> ::= <String>
|
||||
<ASPID> ::= <String>
|
||||
<AssetTag> ::= <String>
|
||||
<BrowserTokenPermanentID> ::= <String>
|
||||
<BuildingID> ::= <String>|id:<String>
|
||||
<CAALevelName> ::= <String>
|
||||
<CalendarACLScope> ::=
|
||||
<EmailAddress>|user:<EmailAddress>|group:<EmailAddress>|
|
||||
domain:<DomainName>|domain|default
|
||||
<CalendarItem> ::= <EmailAddress>
|
||||
<ChannelCustomerID> ::= <String>
|
||||
<ChatEmojiName> ::= :[0-9a-z_-]+:
|
||||
<ChatEmoji> ::= emojiname <ChatEmojiName> | customemojis/<String>
|
||||
<ChatMember> ::= spaces/<String>/members/<String>
|
||||
<ChatMessage> ::= spaces/<String>/messages/<String>
|
||||
<ChatSection> ::= users/<String>/sections/<String> | sections/<String> | section <String>
|
||||
<ChatSectionItem> ::= users/<String>/sections/<String>/items/<String> | sections/<String>/items/<String>
|
||||
<ChatSpace> ::= spaces/<String> | space <String> | space spaces/<String>
|
||||
<ChatThread> ::= spaces/<String>/threads/<String>
|
||||
<ChromeProfilePermanentID> ::= <String>
|
||||
<ChromeProfileName> ::= customers/<CustomerID>/profiles/<ChromeProfilePermanentID> | <ChromeProfilePermanentID>
|
||||
<ChromeProfileCommandName> ::= <ChomeProfileName>/commands/<String>
|
||||
<GIGroupAlias> ::= <EmailAddress>
|
||||
<GIGroupItem> ::= <EmailAddress>|<UniqueID>|groups/<String>
|
||||
<CIGroupMemberType> ::= cbcmbrowser|chromeosdevice|customer|group|other|serviceaccount|user
|
||||
<CIPolicyName> ::= policies/<String>|settings/<String>|<String>
|
||||
<ClassificationLabelID> ::= <String>
|
||||
<ClassificationLabelFieldID> ::= <String>
|
||||
<ClassificationLabelSelectionID> ::= <String>
|
||||
<ClassificationLabelName> ::= labels/<ClassificationLabelID>[@latest|@published|@<Number>]
|
||||
<ClassificationLabelPermissionName> ::= labels/<ClassificationLabelID>[@latest|@published|@<Number>]/permissions/(audiences|groups|people)/<String>
|
||||
<ClassroomInvitationID> ::= <String>
|
||||
<ClientID> ::= <String>
|
||||
<CommandID> ::= <String>
|
||||
<ContactID> ::= <String>
|
||||
<ContactGroupID> ::= id:<String>
|
||||
<ContactGroupName> ::= <String>
|
||||
<ContactGroupItem> ::= <ContactGroupID>|<ContactGroupName>
|
||||
<CorporaAttribute> ::= alldrives|allteamdrives|domain|onlyteamdrives|user
|
||||
<CourseAlias> ::= <String>
|
||||
<CourseAnnouncementContent> ::=
|
||||
((text <String>)|
|
||||
(textfile <FileName> [charset <Charset>])|
|
||||
(gdoc <UserGoogleDoc>)|
|
||||
(gcsdoc <StorageBucketObjectName>))
|
||||
<CourseAnnouncementID> ::= <Number>
|
||||
<CourseAnnouncementState> ::= draft|published|deleted
|
||||
<CourseID> ::= <Number>|d:<CourseAlias>
|
||||
<CourseMaterialID> ::= <Number>
|
||||
<CourseMaterialState> ::= draft|published|deleted
|
||||
<CourseParticipantType> ::= teacher|teachers|student|students
|
||||
<CourseState> ::= active|archived|provisioned|declined|suspended
|
||||
<CourseSubmissionID> ::= <Number>
|
||||
<CourseSubmissionState> ::= new|created|turned_in|returned|reclaimed_by_student
|
||||
<CourseTopic> ::= <String>
|
||||
<CourseTopicID> ::= <Number>
|
||||
<CourseWorkID> ::= <Number>
|
||||
<CourseWorkState> ::= draft|published|deleted
|
||||
<CrOSID> ::= <String>
|
||||
<CustomerID> ::= <String>
|
||||
<DateTimeFormat> ::= <String>
|
||||
See: https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes
|
||||
<DeliverySetting> ::=
|
||||
allmail|
|
||||
abridged|daily|
|
||||
digest|
|
||||
disabled|
|
||||
none|nomail
|
||||
<DeviceID> ::= devices/<String>
|
||||
<DeviceType> ::= android|chrome_os|google_sync|ios|linux|mac_os|windows
|
||||
<DeviceUserID> ::= devices/<String>/deviceUsers/<String>
|
||||
<DomainAlias> ::= <String>
|
||||
<DomainName> ::= <String>(.<String>)+
|
||||
<DriveFileACLRole> ::=
|
||||
commenter|
|
||||
contentmanager|fileorganizer|
|
||||
contributor|editor|writer|
|
||||
manager|organizer|owner|
|
||||
reader|viewer
|
||||
<DriveFileACLType> ::= anyone|domain|group|user
|
||||
<DriveFileID> ::= <String>
|
||||
<DriveFileURL> ::=
|
||||
https://drive.google.com/open?id=<DriveFileID>
|
||||
https://drive.google.com/drive/files/<DriveFileID>
|
||||
https://drive.google.com/drive/folders/<DriveFileID>
|
||||
https://drive.google.com/drive/folders/<DriveFileID>?resourcekey=<String>
|
||||
https://drive.google.com/file/d/<DriveFileID>/<String>
|
||||
https://docs.google.com/document/d/<DriveFileID>/<String>
|
||||
https://docs.google.com/drawings/d/<DriveFileID>/<String>
|
||||
https://docs.google.com/forms/d/<DriveFileID>/<String>
|
||||
https://docs.google.com/presentation/d/<DriveFileID>/<String>
|
||||
https://docs.google.com/spreadsheets/d/<DriveFileID>/<String>
|
||||
<DriveFileItem> ::= <DriveFileID>|<DriveFileURL>
|
||||
<DriveFolderID> ::= <String>
|
||||
<DriveFileName> ::= <String>
|
||||
<DriveFolderName> ::= <String>
|
||||
<DriveFolderPath> ::= <String>(/<String>)*
|
||||
<DriveFilePermission> ::=
|
||||
anyone;<DriveFileACLRole>|
|
||||
anyonewithlink;<DriveFileACLRole>|
|
||||
domain:<DomainName>;<DriveFileACLRole>|
|
||||
domainwithlink:<DomainName>;<DriveFileACLRole>|
|
||||
group:<EmailAddress>;<DriveFileACLRole>|
|
||||
user:<EmailAddress>;<DriveFileACLRole>
|
||||
<DriveFilePermissionID> ::= anyone|anyonewithlink|id:<String>
|
||||
<DriveFilePermissionIDorEmail> ::= <DriveFilePermissionID>|<EmailAddress>
|
||||
<DriveFileRevisionID> ::= <String>
|
||||
<EmailAddress> ::= <String>@<DomainName>
|
||||
<EmailItem> ::= <EmailAddress>|<UniqueID>|<String>
|
||||
<EmailReplacement> ::= <String>
|
||||
<EventID> ::= <String>
|
||||
<EventName> ::= <String>
|
||||
<ExportItem> ::= <UniqueID>|<String>
|
||||
<ExportStatus> ::= completed|failed|inprogrsss
|
||||
<FeatureName> ::= <String>
|
||||
<FieldName> ::= <String>
|
||||
<FileName> ::= <String>
|
||||
<FileNamePattern> ::= <String>
|
||||
<FilterID> ::= <String>
|
||||
<FloorName> ::= <String>
|
||||
<GroupItem> ::= <EmailAddress>|<UniqueID>|<String>
|
||||
<GroupRole> ::= owner|manager|member
|
||||
<GroupMemberType> ::= customer|group|user
|
||||
<GuardianItem> ::= <EmailAddress>|<UniqueID>|<String>
|
||||
<GuardianInvitationID> ::= <String>
|
||||
<HoldItem> ::= <UniqueID>|<String>
|
||||
<HostName> ::= <String>
|
||||
<iCalUID> ::= <String>
|
||||
<JSONData> ::= (json [charset <Charset>] <String>) | (json file <FileName> [charset <Charset>]) |
|
||||
<Key> ::= <String>
|
||||
<LabelID> ::= Label_<String>
|
||||
<LabelName> ::= <String>
|
||||
<LabelReplacement> ::= <String>
|
||||
<LookerStudioAssetID> ::= <String>
|
||||
<LookerStudioPermission> ::=
|
||||
user:<EmailAddress>|
|
||||
group:<EmailAddress>|
|
||||
domain:<DomainName>|
|
||||
serviceAccount:<EmailAddress>
|
||||
<Marker> ::= <String>
|
||||
<MatterItem> ::= <UniqueID>|<String>
|
||||
<MatterState> ::= open|closed|deleted
|
||||
<MeetConferenceName> ::= conferenceRecords/<String>
|
||||
<MeetID> ::= <String>
|
||||
Must match this Python Regular Expression: [a-z]{3}-[a-z]{4}-[a-z]{3}
|
||||
<MeetSpaceName> ::= spaces/<String> | <String>
|
||||
<MessageContent> ::=
|
||||
(message|textmessage|htmlmessage <String>)|
|
||||
(file|textfile|htmlfile <FileName> [charset <Charset>])|
|
||||
(gdoc|ghtml <UserGoogleDoc>)|
|
||||
(gcsdoc|gcshtml <StorageBucketObjectName>)
|
||||
<MessageID> ::= <String>
|
||||
<Namespace> ::= <String>
|
||||
<NotesName> ::= notes/<String>
|
||||
<NotifyMessageContent> ::=
|
||||
(message|textmessage|htmlmessage <String>)|
|
||||
(file|textfile|htmlfile <FileName> [charset <Charset>])|
|
||||
(gdoc|ghtml <UserGoogleDoc>)|
|
||||
(gcsdoc|gcshtml <StorageBucketObjectName>)
|
||||
<NumberOfSeats> ::= <Number>
|
||||
<NumberRange> ::= <Number>|(<Number>/<Number>)
|
||||
<OrgUnitID> ::= id:<String>
|
||||
<OrgUnitPath> ::= /|(/<String>)+
|
||||
<OrgUnitItem> ::= <OrgUnitID>|<OrgUnitPath>
|
||||
<OtherContactsResourceName> ::= otherContacts/<String>
|
||||
<ParameterKey> ::= <String>
|
||||
<ParameterValue> ::= <String>
|
||||
<Password> ::= <String>
|
||||
<PeopleResourceName> ::= people/<String>
|
||||
<PrinterID> ::= <String>
|
||||
<ProjectID> ::= <String>
|
||||
Must match this Python Regular Expression: [a-z][a-z0-9-]{4,28}[a-z0-9]
|
||||
<ProjectName> ::= <String>
|
||||
Must match this Python Regular Expression: [a-zA-Z0-9 '"!-]{4,30}
|
||||
<PropertyKey> ::= <String>
|
||||
<PropertyValue> ::= <String>
|
||||
<PubSubTopicName> ::= <String>
|
||||
<QueryAlert> ::= <String>
|
||||
See: https://developers.google.com/admin-sdk/alertcenter/guides/query-filters
|
||||
<QueryBrowser> ::= <String>
|
||||
See: https://support.google.com/chrome/a/answer/9681204#retrieve_all_chrome_devices_for_an_account
|
||||
<QueryBrowserToken> ::= <String>
|
||||
See: https://support.google.com/chrome/a/answer/9949706?ref_topic=9301744
|
||||
<QueryCalendar> ::= <String>
|
||||
<QueryCEL> ::= <String>
|
||||
See: https://cloud.google.com/access-context-manager/docs/custom-access-level-spec
|
||||
<QueryContact> ::= <String>
|
||||
See: https://developers.google.com/google-apps/contacts/v3/reference#contacts-query-parameters-reference
|
||||
<QueryCrOS> ::= <String>:<String>
|
||||
See: https://support.google.com/chrome/a/answer/1698333
|
||||
<QueryDevice> ::= <String>:<String>
|
||||
See: https://support.google.com/a/answer/7549103
|
||||
<QueryDriveFile> ::= <String>
|
||||
See: https://developers.google.com/drive/api/v3/search-files
|
||||
<QueryDynamicGroup> ::= <String>
|
||||
See: https://cloud.google.com/identity/docs/reference/rest/v1/groups#dynamicgroupquery
|
||||
<QueryGmail> ::= <String>
|
||||
See: https://support.google.com/mail/answer/7190
|
||||
<QueryGroup> ::= <String>
|
||||
See: https://developers.google.com/admin-sdk/directory/v1/guides/search-groups
|
||||
<QueryItem> ::= <UniqueID>|<String>
|
||||
<QueryMemberRestrictions> ::= <String>
|
||||
See: https://cloud.google.com/identity/docs/reference/rest/v1beta1/SecuritySettings#MemberRestriction
|
||||
<QueryMobile> ::= <String>:<String>
|
||||
See: https://support.google.com/a/answer/7549103
|
||||
<QueryTeamDrive> ::= <String>
|
||||
See: https://developers.google.com/drive/api/v3/search-parameters
|
||||
<QueryUser> ::= <String>
|
||||
See: https://developers.google.com/admin-sdk/directory/v1/guides/search-users
|
||||
<QueryVaultCorpus> ::= <String>
|
||||
See: https://developers.google.com/vault/reference/rest/v1/matters.holds#CorpusQuery
|
||||
<RequestID> ::= <String>
|
||||
<ResellerID> ::= <String>
|
||||
<ResourceID> ::= <String>
|
||||
<SchemaName> ::= <String>
|
||||
<SchemaNameField> ::= <SchemaName>.<FieldName>
|
||||
<Section> ::= <String>
|
||||
<SendAsContent> ::=
|
||||
(sig|signature|htmlsig <String>)|
|
||||
(file|htmlfile <FileName> [charset <Charset>])|
|
||||
(gdoc|ghtml <UserGoogleDoc>)|
|
||||
(gcsdoc|gcshtml <StorageBucketObjectName>)
|
||||
<SerialNumber> ::= <String>
|
||||
<ServiceAccountName> ::= <String>
|
||||
Must match this Python Regular Expression: [a-z][a-z0-9-]{4,28}[a-z0-9]
|
||||
<ServiceAccountDisplayName> ::= <String>
|
||||
Maximum of 100 characters
|
||||
<ServiceAccountDescrition> ::= <String>
|
||||
Maximum of 256 chcracters
|
||||
<ServiceAccountEmail> ::= <ServiceAccountName>@<ProjectID>.iam.gserviceaccount.com
|
||||
<ServiceAccountUniqueID> ::= <Number>
|
||||
<ServiceAccountKey> ::= <String>
|
||||
<SheetEntity> ::= <String>|id:<Number>
|
||||
<SignatureContent> ::=
|
||||
(<String>)|
|
||||
(file|htmlfile <FileName> [charset <Charset>])|
|
||||
(gdoc|ghtml <UserGoogleDoc>)|
|
||||
(gcsdoc|gcshtml <StorageBucketObjectName>)
|
||||
<SiteACLScope> ::=
|
||||
<EmailAddress>|user:<EmailAddress>|group:<EmailAddress>|
|
||||
domain:<DomainName>|domain|default
|
||||
<SiteItem> ::= [<DomainName>/]<SiteName>
|
||||
<S/MIMEID> ::= <String>
|
||||
<SMTPHostName> ::= <String>
|
||||
<StudentGroupID> ::= <Number>
|
||||
<StudentItem> ::= <EmailAddress>|<UniqueID>|<String>
|
||||
<SharedDriveACLRole> ::=
|
||||
commenter|
|
||||
contentmanager|fileorganizer|
|
||||
contributor|editor|writer|
|
||||
manager|organizer|owner|
|
||||
reader|viewer
|
||||
<SharedDriveID> ::= <String>
|
||||
<SharedDriveName> ::= <String>
|
||||
<StorageBucketName> ::= <String>
|
||||
<StorageObjectName> ::= <String>
|
||||
<StorageBucketObjectName> ::=
|
||||
https://storage.cloud.google.com/<StorageBucketName>/<StorageObjectName>|
|
||||
https://storage.googleapis.com/<StorageBucketName>/<StorageObjectName>|
|
||||
gs://<StorageBucketName>/<StorageObjectName>|
|
||||
<StorageBucketName>/<StorageObjectName>
|
||||
<Tag> ::= <String>
|
||||
<TagManagerAccountID> ::= <String>
|
||||
<TagManagerAccountPath> ::= accounts/<TagManagerAccountID>
|
||||
<TagManagerContainerID> ::= <String>
|
||||
<TagManagerContainerPath> ::= accounts/<TagManagerAccountID>/containers/<TagManagerContainerID>
|
||||
<TagManagerWorkspaceID> ::= <String>
|
||||
<TagManagerWorkspacePath> ::= accounts/<TagManagerAccountID>/containers/<TagManagerContainerID>/workspaces/<TagManagerWorkspaceID>
|
||||
<TagManagerTagID> ::= <String>
|
||||
<TagManagerTagPath> ::= accounts/<TagManagerAccountID>/containers/<TagManagerContainerID>/workspaces/<TagManagerWorkspaceID>/tags/<TagManagerTagID>
|
||||
<TakeoutBucketName> ::= takeout-export-[a-f,0-9,-]*
|
||||
<TaskID> ::= <String>
|
||||
<TaskListID> ::= <String>
|
||||
<TaskListTitle> ::= tltitle:<String>
|
||||
<TasklistIDTaskID> ::= <TasklistID>/<TaskID>
|
||||
<ThreadID> ::= <String>
|
||||
<TimeZone> ::= <String>
|
||||
See: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
<Title> ::= <String>
|
||||
<ToDriveAttribute> ::=
|
||||
(tdaddsheet [<Boolean>])|
|
||||
(tdalert <EmailAddress>)*|
|
||||
(tdbackupsheet (id:<Number>)|<String>)|
|
||||
(tdcellnumberformat text|number)|
|
||||
(tdcellwrap clip|overflow|wrap)|
|
||||
(tdclearfilter [<Boolean>])|
|
||||
(tdcopysheet (id:<Number>)|<String>)|
|
||||
(tddescription <String>)|
|
||||
(tdfileid <DriveFileID>)|
|
||||
(tdfrom <EmailAddress>)|
|
||||
(tdlocalcopy [<Boolean>])|
|
||||
(tdlocale <Locale>)|
|
||||
(tdnobrowser [<Boolean>])|
|
||||
(tdnoemail [<Boolean>])|
|
||||
(tdnoescapechar [<Boolean>])|
|
||||
(tdnotify [<Boolean>])|
|
||||
(tdparent (id:<DriveFolderID>)|<DriveFolderName>)|
|
||||
(tdretaintitle [<Boolean>])|
|
||||
(tdreturnidonly [<Boolean>])|
|
||||
(tdshare <EmailAddress> commenter|reader|writer)*|
|
||||
(tdsheet (id:<Number>)|<String>)|
|
||||
(tdsheettimestamp [<Boolean>] [tdsheettimeformat <DateTimeFormat>])
|
||||
(tdsheettitle <String>)|
|
||||
(tdsubject <String>)|
|
||||
([tdsheetdaysoffset <Number>] [tdsheethoursoffset <Number>])|
|
||||
(tdtimestamp [<Boolean>] [tdtimeformat <DateTimeFormat>]
|
||||
[tddaysoffset <Number>] [tdhoursoffset <Number>])|
|
||||
(tdtimezone <TimeZone>)|
|
||||
(tdtitle <String>)|
|
||||
(tdupdatesheet [<Boolean>])|
|
||||
(tduploadnodata [<Boolean>])|
|
||||
(tduser <EmailAddress>)
|
||||
<TransferID> ::= <String>
|
||||
<URI> ::= <String>
|
||||
<URL> ::= <String>
|
||||
<UserItem> ::= <EmailAddress>|<UniqueID>|<String>
|
||||
<UserName> ::= <String>
|
||||
<VacationMessageContent> ::=
|
||||
(message|textmessage|htmlmessage <String>)|
|
||||
(file|textfile|htmlfile <FileName> [charset <Charset>])|
|
||||
(gdoc|ghtml <UserGoogleDoc>)|
|
||||
(gcsdoc|gcshtml <StorageBucketObjectName>)
|
||||
<YouTubeChannelID> ::= <String>
|
||||
```
|
||||
@@ -1,181 +0,0 @@
|
||||
# Bulk Processing
|
||||
- [Introduction](#introduction)
|
||||
- [Python Regular Expressions](Python-Regular-Expressions)
|
||||
- [GAM Configuration](gam.cfg)
|
||||
- [Meta Commands and File Redirection](Meta-Commands-and-File-Redirection)
|
||||
- [Definitions](#definitions)
|
||||
- [Batch files](#batch-files)
|
||||
- [CSV files](#csv-files)
|
||||
- [CSV files with redirection and select](#csv-files-with-redirection-and-select)
|
||||
- [Automatic batch processing](#automatic-batch-processing)
|
||||
- [Process Google Sheet commands and save results](#process-google-sheet-commands-and-save-results)
|
||||
|
||||
## Introduction
|
||||
Batch and CSV file processing can improve performance by executing Gam commands in parallel.
|
||||
The variables `num_threads`, `num_tbatch_threads` and `auto_batch_min` in `gam.cfg` control parallelism.
|
||||
|
||||
## Definitions
|
||||
* [Command data from Google Docs/Sheets/Storage](Command-Data-From-Google-Docs-Sheets-Storage)
|
||||
`gdoc <UserGoogleDoc>` and `gsheet <UserGoogleSheet>`
|
||||
|
||||
<DateTimeFormat> ::= <String>
|
||||
See: https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes
|
||||
|
||||
## Batch files
|
||||
There are two types of batch processing, one that uses processes and one that uses threads. Using processes is higher performance but `gam csv` commands are not supported.
|
||||
* `gam batch` - gam commands are run as processes, gam csv commands are not allowed in the batch file
|
||||
* `gam tbatch` - gam commands are run as threads, gam csv commands are allowed in the batch file
|
||||
```
|
||||
gam batch <FileName>|-|(gdoc <UserGoogleDoc>) [charset <Charset>] [showcmds [<Boolean>]]
|
||||
gam tbatch <FileName>|-|(gdoc <UserGoogleDoc>) [charset <Charset>] [showcmds [<Boolean>]]
|
||||
```
|
||||
* `<FileName>` - A flat file containing Gam commands
|
||||
* `-` - Gam commands coming from stdin
|
||||
* `gdoc <UserGoogleDoc>` - A Google Doc containing Gam commands
|
||||
* `showcmds` - Write `timestamp,command number/number of commands,command` to stderr when each command starts; write `timestamp, command number/numberof commands,complete` to stderr when command completes
|
||||
|
||||
Batch files can contain the following types of lines:
|
||||
* Blank lines - Ignored
|
||||
* \# Comment line - Ignored
|
||||
* gam \<GAMArgumentList\> - Execute a GAM command
|
||||
* File path arguments in \<GAMArgumentList\> should be enclosed in \"
|
||||
* commit-batch
|
||||
* GAM waits for all running GAM commands to complete
|
||||
* GAM continues
|
||||
* commit-batch \<String\>
|
||||
* GAM waits for all running GAM commands to complete
|
||||
* GAM prints \<String\> and waits for the user to press any key
|
||||
* GAM continues
|
||||
* sleep \<Integer\> - Batch processing will suspend for \<Integer\> seconds before the next command line is processed
|
||||
* To be effective, this should immediately follow commit-batch
|
||||
* print \<String\> - Print \<String\> on stderr
|
||||
* datetime \<DateTimeFormat\>
|
||||
* The current time is formatted with \<DateTimeFormat\> and subsequent lines will have `%datetime%` replaced with the formatted time value.
|
||||
* See: https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes
|
||||
* set \<KeywordString\> \<ValueString\>
|
||||
* Subsequent lines will have %\<KeywordString\>% replaced with \<ValueString\>
|
||||
* clear \<KeywordString\>
|
||||
* Subsequent lines will not be scanned for %\<KeywordString\>%
|
||||
|
||||
Tbatch files can also contain the following line:
|
||||
* execute \<Program\> \<ArgumentList\> - Execute an arbitrary command; use the full path to specify \<Program\>
|
||||
|
||||
### Example
|
||||
* You need to create accounts for your new students and assign them to groups based on their graduation year.
|
||||
* You have a CSV file NewStudents.csv with columns: Email,First,Last,GradYear,Password
|
||||
* You have a batch file NewStudents.bat containing these commands:
|
||||
```
|
||||
gam csv NewStudents.csv gam create user "~Email" firstname "~First" lastname "~Last" org "/Students/~~GradYear~~" password "~Password"
|
||||
commit-batch
|
||||
gam update group seniors sync members ou /Students/2020
|
||||
gam update group juniors sync members ou /Students/2021
|
||||
gam update group sophomores sync members ou /Students/2022
|
||||
gam update group highschool sync members ous "'/Students/2020','/Students/2021','/Students/2022'"
|
||||
```
|
||||
* Execute the batch file
|
||||
```
|
||||
gam redirect stdout ./NewStudents.out redirect stderr ./NewStudents.err tbatch NewStudents.bat showcmds
|
||||
```
|
||||
## CSV files
|
||||
```
|
||||
gam csv <FileName>|-|(gsheet <UserGoogleSheet>)|(gdoc <UserGoogleDoc>) [charset <Charset>] [warnifnodata]
|
||||
[columndelimiter <Character>] [noescapechar <Boolean>] [quotechar <Character>] [fields <FieldNameList>]
|
||||
(matchfield|skipfield <FieldName> <RESearchPattern>)* [showcmds [<Boolean>]]
|
||||
[skiprows <Integer>] [maxrows <Integer>]
|
||||
gam <GAMArgumentList>
|
||||
|
||||
gam loop <FileName>|-|(gsheet <UserGoogleSheet>)|(gdoc <UserGoogleDoc>) [charset <Charset>] [warnifnodata]
|
||||
[columndelimiter <Character>] [noescapechar <Boolean>] [quotechar <Character>] [fields <FieldNameList>]
|
||||
(matchfield|skipfield <FieldName> <RESearchPattern>)* [showcmds [<Boolean>]]
|
||||
[skiprows <Integer>] [maxrows <Integer>]
|
||||
gam <GAMArgumentList>
|
||||
```
|
||||
* `gam csv` - Use parallel processing
|
||||
* `gam loop` - Use serial processing
|
||||
* `<FileName>` - A CSV file and the one or more columns that contain data
|
||||
* `-` - The one or more columns that contain data from stdin
|
||||
* `gsheet <UserGoogleSheet>` - A Google Sheet and the one or more columns that contain data
|
||||
* `gdoc <UserGoogleDoc>` - A Google Doc and the one or more columns that contain data
|
||||
* `columndelimiter <Character>` - Columns are separated by `<Character>`; if not specified, the value of `csv_input_column_delimiter` from `gam.cfg` will be used
|
||||
* `noescapechar <Boolean>` - Should `\` be ignored as an escape character; if not specified, the value of `csv_input_no_escape_char` from `gam.cfg` will be used
|
||||
* `quotechar <Character>` - The column quote characer is `<Character>`; if not specified, the value of `csv_input_quote_char` from `gam.cfg` will be used
|
||||
* `fields <FieldNameList>` - The column headings of a CSV file that does not contain column headings.
|
||||
* `(matchfield|skipfield <FieldName> <RESearchPattern>)*` - The criteria to select rows from the CSV file; can be used multiple times; if not specified, all rows are selected
|
||||
* `showcmds` - Write `timestamp,command number/number of commands,command` to stderr when each command starts; write `timestamp, command number/numberof commands,complete` to stderr when command completes
|
||||
* `skiprows <Integer>` - Skip filtered rows from the CSV file/Google Sheet.
|
||||
* `skiprows 0` - All rows are processed, this is the default
|
||||
* `skiprows N` - The first N filtered rows are skipped
|
||||
* `maxrows <Integer>` - Limit the number of filtered rows processed from the CSV file/Google Sheet after any skipped rows.
|
||||
* `maxrows 0` - All rows are processed, this is the default
|
||||
* `maxrows N` - N filtered rows are processed
|
||||
|
||||
### Use CSV file values in command line
|
||||
You can make substitutions in `<GAMArgumentList>` with values from the CSV file.
|
||||
- Reference the field xxx with `~xxx` if the argument contains no other text
|
||||
- Reference the field xxx with `~~xxx~~` if the argument contains other text
|
||||
- An argument containing exactly `~xxx` is replaced by the value of field xxx
|
||||
- An argument containing instances of `~~xxx~~` has `~~xxx~~` replaced by the value of field xxx
|
||||
- An argument containing instances of `~~xxx~!~pattern~!~replacement~~` has `~~xxx~!~pattern~!~replacement~~` replaced by re.sub(pattern, replacement, value of field xxx) See: https://docs.python.org/3/library/re.html
|
||||
|
||||
If an argument is specifying a file path and it starts with a `~`, e.g., `targetfolder "~/Documents/GamWork"`, GAM will flag it as an error:
|
||||
```
|
||||
ERROR: Header "/Documents/GamWork/" not found in CSV headers of "Owner,id,title".
|
||||
```
|
||||
Put a space in front of the `~`: `targetfolder " ~/Documents/GamWork"` to avoid the error.
|
||||
|
||||
### Example
|
||||
* You need to update the work addresses of a set of users
|
||||
* You want a note field that shows their email address as name AT domain.com
|
||||
* You have a CSV file Users.csv with columns: primaryEmail,Street,City,State,ZIP
|
||||
```
|
||||
gam csv Users.csv gam update user "~primaryEmail" address type work unstructured "~~Street~~, ~~City~~, ~~State~~ ~~ZIP~~" primary note text_plain "~~primaryEmail~!~^(.+)@(.+)$~!~\1 AT \2~~"
|
||||
```
|
||||
* You want to do the above using a Google Sheet
|
||||
```
|
||||
gam csv gsheet <user> <fileID> "<sheetName>" gam update user "~primaryEmail" address type work unstructured "~~Street~~, ~~City~~, ~~State~~ ~~ZIP~~" primary note text_plain "~~primaryEmail~!~^(.+)@(.+)$~!~\1 AT \2~~"
|
||||
```
|
||||
|
||||
## CSV files with redirection and select
|
||||
You should use the `multiprocess` option on any redirected files: `csv`, `stdout`, `stderr`.
|
||||
```
|
||||
gam redirect csv ./filelistperms.csv multiprocess csv Users.csv gam user "~primaryEmail" print filelist fields id,name,mimetype,basicpermissions
|
||||
gam redirect csv - multiprocess todrive csv Users.csv gam user "~primaryEmail" print filelist fields id,name,mimetype,basicpermissions
|
||||
```
|
||||
|
||||
If you want to select a `gam.cfg` section for the command, you can select the section at the outer `gam` and save it
|
||||
or select the section at the inner `gam`.
|
||||
```
|
||||
gam select <Section> save redirect csv ./filelistperms.csv multiprocess csv Users.csv gam user "~primaryEmail" print filelist fields id,name,mimetype,basicpermissions
|
||||
gam redirect csv ./filelistperms.csv multiprocess csv Users.csv gam select <Section> user "~primaryEmail" print filelist fields id,name,mimetype,basicpermissions
|
||||
gam select <Section> save redirect csv - multiprocess todrive csv Users.csv gam user "~primaryEmail" print filelist fields id,name,mimetype,basicpermissions
|
||||
gam redirect csv - multiprocess todrive csv Users.csv gam select <Section> user "~primaryEmail" print filelist fields id,name,mimetype,basicpermissions
|
||||
```
|
||||
|
||||
## Automatic batch processing
|
||||
You can enable automatic batch (parallel) processing when issuing commands of the form `gam <UserTypeEntity> ...`.
|
||||
In the following example, if the number of users in group sales@domain.com exceeds 1, then the `print filelist` command will be processed in parallel.
|
||||
```
|
||||
gam config auto_batch_min 1 redirect csv ./filelistperms.csv multiprocess group sales@domain.com print filelist fields id,name,mimetype,basicpermissions
|
||||
gam config auto_batch_min 1 redirect csv - multiprocess todrive group sales@domain.com print filelist fields id,name,mimetype,basicpermissions
|
||||
```
|
||||
With automatic batch processing, you should use the `multiprocess` option on any redirected files: `csv`, `stdout`, `stderr`.
|
||||
|
||||
If you want to select a `gam.cfg` section for the command, you must select and save it for it to be processed correctly.
|
||||
```
|
||||
gam select <Section> save config auto_batch_min 1 redirect csv ./filelistperms.csv multiprocess group sales@domain.com print filelist fields id,name,mimetype,basicpermissions
|
||||
```
|
||||
|
||||
## Process Google Sheet commands and save results
|
||||
You want to process data from a Google Sheet tab and save the results to another tab in the same sheet.
|
||||
Make a Google sheet with two tabs: Commands, Results; get the File ID and the two tab IDs.
|
||||
Put your command data in the Commands tab.
|
||||
|
||||
Run your command, write the results to Results.txt
|
||||
```
|
||||
gam redirect stdout ./Results.txt multiprocess redirect stderr stdout csv gsheet user@domain.com <FileID> id:<CommandsTabID> gam ... Command
|
||||
```
|
||||
|
||||
Upload Results.txt to the Results tab of the sheet.
|
||||
```
|
||||
gam user user@domain.com update drivefile <FileID> localfile Results.txt retainname gsheet id:<ResultsTabID>
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user