├── mirror_api_server ├── emulator │ ├── __init__.py │ ├── static │ │ ├── images │ │ │ ├── corner.png │ │ │ ├── error.png │ │ │ ├── reply.png │ │ │ ├── share.png │ │ │ ├── talk.png │ │ │ ├── map_dot.png │ │ │ ├── navigate.png │ │ │ ├── success.png │ │ │ └── read_aloud.png │ │ ├── inner.html │ │ ├── demo.html │ │ └── grammar.grxml │ └── templates │ │ └── glass.html ├── service │ ├── __init__.py │ ├── static │ │ ├── images │ │ │ ├── cat.png │ │ │ └── sepia.jpg │ │ └── service.css │ ├── templates │ │ └── service.html │ ├── notify.py │ └── upload.py ├── mirror_api │ ├── __init__.py │ └── api.py ├── lib │ ├── apiclient │ │ ├── __init__.py │ │ └── errors.py │ ├── oauth2client │ │ ├── __init__.py │ │ ├── anyjson.py │ │ ├── gce.py │ │ ├── keyring_storage.py │ │ ├── file.py │ │ ├── xsrfutil.py │ │ ├── django_orm.py │ │ └── clientsecrets.py │ ├── httplib2 │ │ └── iri2uri.py │ └── uritemplate │ │ └── __init__.py ├── res │ ├── cat1.png │ ├── cat2.png │ ├── cat3.png │ ├── cat4.png │ ├── cat5.png │ └── cat6.png ├── demos │ ├── __init__.py │ ├── templates │ │ └── place.html │ ├── friend_finder.py │ ├── add_a_cat.py │ └── instaglass.py ├── client_secrets.json ├── index.yaml ├── app.yaml ├── main.py └── utils.py ├── examples ├── hangout_companion │ ├── web │ │ ├── hangout_companion.css │ │ ├── hangout_companion.xml │ │ └── src │ │ │ └── HangoutOAuth2.dart │ ├── pubspec.yaml.real │ ├── pubspec.yaml │ ├── pubspec.yaml.test │ └── README.md ├── colours-of-the-world │ ├── lib │ │ ├── apiclient │ │ │ ├── __init__.py │ │ │ └── errors.py │ │ ├── oauth2client │ │ │ ├── __init__.py │ │ │ ├── anyjson.py │ │ │ ├── gce.py │ │ │ ├── keyring_storage.py │ │ │ ├── file.py │ │ │ ├── xsrfutil.py │ │ │ ├── django_orm.py │ │ │ └── clientsecrets.py │ │ ├── httplib2 │ │ │ └── iri2uri.py │ │ └── uritemplate │ │ │ └── __init__.py │ ├── static │ │ └── images │ │ │ ├── red.png │ │ │ ├── blue.png │ │ │ ├── card.png │ │ │ ├── green.png │ │ │ ├── indigo.png │ │ │ ├── orange.png │ │ │ ├── signin.png │ │ │ ├── violet.png │ │ │ ├── yellow.png │ │ │ └── background.png │ ├── index.yaml │ ├── client_secrets.json │ ├── app.yaml │ ├── main.py │ ├── models.py │ ├── notify.py │ ├── templates │ │ └── service.html │ ├── utils.py │ └── service.py └── hangout-comment-tracker │ ├── lib │ ├── apiclient │ │ ├── __init__.py │ │ └── errors.py │ ├── oauth2client │ │ ├── __init__.py │ │ ├── anyjson.py │ │ ├── gce.py │ │ ├── keyring_storage.py │ │ ├── file.py │ │ ├── xsrfutil.py │ │ ├── django_orm.py │ │ └── clientsecrets.py │ └── httplib2 │ │ └── iri2uri.py │ ├── static │ ├── images │ │ ├── gplus.png │ │ ├── comment.png │ │ ├── noimage.png │ │ ├── search.png │ │ ├── spinner.gif │ │ ├── youtube.png │ │ └── youtube_author.png │ └── service.css │ ├── client_secrets.json │ ├── app.yaml │ ├── main.py │ ├── templates │ └── service.html │ ├── notify.py │ ├── models.py │ ├── utils.py │ └── service.py ├── .gitignore ├── .gitmodules ├── CONTRIBUTORS.md └── utils ├── README.md ├── post-commit └── pre-commit /mirror_api_server/emulator/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mirror_api_server/service/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mirror_api_server/mirror_api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/hangout_companion/web/hangout_companion.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mirror_api_server/lib/apiclient/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.1" 2 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/lib/apiclient/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.1" 2 | -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/lib/apiclient/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.1" 2 | -------------------------------------------------------------------------------- /mirror_api_server/res/cat1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/mirror_api_server/res/cat1.png -------------------------------------------------------------------------------- /mirror_api_server/res/cat2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/mirror_api_server/res/cat2.png -------------------------------------------------------------------------------- /mirror_api_server/res/cat3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/mirror_api_server/res/cat3.png -------------------------------------------------------------------------------- /mirror_api_server/res/cat4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/mirror_api_server/res/cat4.png -------------------------------------------------------------------------------- /mirror_api_server/res/cat5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/mirror_api_server/res/cat5.png -------------------------------------------------------------------------------- /mirror_api_server/res/cat6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/mirror_api_server/res/cat6.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | _client_secrets.json 3 | mirror_api_server/endpoints_proto_datastore 4 | packages 5 | pubspec.lock 6 | -------------------------------------------------------------------------------- /mirror_api_server/service/static/images/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/mirror_api_server/service/static/images/cat.png -------------------------------------------------------------------------------- /examples/colours-of-the-world/static/images/red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/examples/colours-of-the-world/static/images/red.png -------------------------------------------------------------------------------- /mirror_api_server/emulator/static/images/corner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/mirror_api_server/emulator/static/images/corner.png -------------------------------------------------------------------------------- /mirror_api_server/emulator/static/images/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/mirror_api_server/emulator/static/images/error.png -------------------------------------------------------------------------------- /mirror_api_server/emulator/static/images/reply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/mirror_api_server/emulator/static/images/reply.png -------------------------------------------------------------------------------- /mirror_api_server/emulator/static/images/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/mirror_api_server/emulator/static/images/share.png -------------------------------------------------------------------------------- /mirror_api_server/emulator/static/images/talk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/mirror_api_server/emulator/static/images/talk.png -------------------------------------------------------------------------------- /mirror_api_server/service/static/images/sepia.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/mirror_api_server/service/static/images/sepia.jpg -------------------------------------------------------------------------------- /examples/colours-of-the-world/static/images/blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/examples/colours-of-the-world/static/images/blue.png -------------------------------------------------------------------------------- /examples/colours-of-the-world/static/images/card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/examples/colours-of-the-world/static/images/card.png -------------------------------------------------------------------------------- /examples/colours-of-the-world/static/images/green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/examples/colours-of-the-world/static/images/green.png -------------------------------------------------------------------------------- /mirror_api_server/emulator/static/images/map_dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/mirror_api_server/emulator/static/images/map_dot.png -------------------------------------------------------------------------------- /mirror_api_server/emulator/static/images/navigate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/mirror_api_server/emulator/static/images/navigate.png -------------------------------------------------------------------------------- /mirror_api_server/emulator/static/images/success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/mirror_api_server/emulator/static/images/success.png -------------------------------------------------------------------------------- /examples/colours-of-the-world/static/images/indigo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/examples/colours-of-the-world/static/images/indigo.png -------------------------------------------------------------------------------- /examples/colours-of-the-world/static/images/orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/examples/colours-of-the-world/static/images/orange.png -------------------------------------------------------------------------------- /examples/colours-of-the-world/static/images/signin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/examples/colours-of-the-world/static/images/signin.png -------------------------------------------------------------------------------- /examples/colours-of-the-world/static/images/violet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/examples/colours-of-the-world/static/images/violet.png -------------------------------------------------------------------------------- /examples/colours-of-the-world/static/images/yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/examples/colours-of-the-world/static/images/yellow.png -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/static/images/gplus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/examples/hangout-comment-tracker/static/images/gplus.png -------------------------------------------------------------------------------- /mirror_api_server/emulator/static/images/read_aloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/mirror_api_server/emulator/static/images/read_aloud.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "endpoints-proto-datastore"] 2 | path = endpoints-proto-datastore 3 | url = https://github.com/GoogleCloudPlatform/endpoints-proto-datastore.git 4 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/static/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/examples/colours-of-the-world/static/images/background.png -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/static/images/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/examples/hangout-comment-tracker/static/images/comment.png -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/static/images/noimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/examples/hangout-comment-tracker/static/images/noimage.png -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/static/images/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/examples/hangout-comment-tracker/static/images/search.png -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/static/images/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/examples/hangout-comment-tracker/static/images/spinner.gif -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/static/images/youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/examples/hangout-comment-tracker/static/images/youtube.png -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/static/images/youtube_author.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspect/mirror-api/master/examples/hangout-comment-tracker/static/images/youtube_author.png -------------------------------------------------------------------------------- /mirror_api_server/lib/oauth2client/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.1" 2 | 3 | GOOGLE_AUTH_URI = 'https://accounts.google.com/o/oauth2/auth' 4 | GOOGLE_REVOKE_URI = 'https://accounts.google.com/o/oauth2/revoke' 5 | GOOGLE_TOKEN_URI = 'https://accounts.google.com/o/oauth2/token' 6 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | [Danny Hermes](https://github.com/dhermes) 2 | 3 | [Gerwin Sturm](https://github.com/Scarygami) 4 | 5 | [Greg Roberts](https://github.com/acroyogi) 6 | 7 | [Martin Matysiak](https://github.com/kaktus621) 8 | 9 | [Tim Wintle](https://github.com/timwintle) 10 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/index.yaml: -------------------------------------------------------------------------------- 1 | indexes: 2 | 3 | - kind: Submission 4 | properties: 5 | - name: colour 6 | - name: date 7 | direction: desc 8 | 9 | - kind: Submission 10 | ancestor: yes 11 | properties: 12 | - name: date 13 | direction: desc 14 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/lib/oauth2client/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.1" 2 | 3 | GOOGLE_AUTH_URI = 'https://accounts.google.com/o/oauth2/auth' 4 | GOOGLE_REVOKE_URI = 'https://accounts.google.com/o/oauth2/revoke' 5 | GOOGLE_TOKEN_URI = 'https://accounts.google.com/o/oauth2/token' 6 | -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/lib/oauth2client/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.1" 2 | 3 | GOOGLE_AUTH_URI = 'https://accounts.google.com/o/oauth2/auth' 4 | GOOGLE_REVOKE_URI = 'https://accounts.google.com/o/oauth2/revoke' 5 | GOOGLE_TOKEN_URI = 'https://accounts.google.com/o/oauth2/token' 6 | -------------------------------------------------------------------------------- /examples/hangout_companion/pubspec.yaml.real: -------------------------------------------------------------------------------- 1 | name: hangout_companion 2 | version: 0.0.1-dev 3 | description: An example of a Hangout application that accesses the Mirror API. 4 | 5 | dependencies: 6 | browser: any 7 | google_plus_v1_api: any 8 | google_mirror_v1_api: any 9 | hangouts_api: 10 | git: git://github.com/Scarygami/dart_hangouts_api.git 11 | -------------------------------------------------------------------------------- /utils/README.md: -------------------------------------------------------------------------------- 1 | 1. Copy those two files to `.git/hooks`. 2 | 2. Create copies of the template `client_secrets.json` files and name them `_client_secrets.json` 3 | 3. Edit the `client_secrets.json` files with your actual data. 4 | 4. Commit as you normally would, the pre/post commit scripts will make 5 | sure that only the template files are commited and not your actual secrets. -------------------------------------------------------------------------------- /mirror_api_server/demos/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | DEMOS = ["add_a_cat", "instaglass", "friend_finder", "check_in"] 3 | 4 | demo_services = [] 5 | for demo in DEMOS: 6 | demo_services.append(__import__("demos." + demo, fromlist="*")) 7 | 8 | DEMO_ROUTES = [] 9 | for demo_service in demo_services: 10 | if hasattr(demo_service, "ROUTES"): 11 | DEMO_ROUTES.extend(demo_service.ROUTES) 12 | -------------------------------------------------------------------------------- /examples/hangout_companion/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: hangout_companion 2 | version: 0.0.1-dev 3 | description: An example of a Hangout application that accesses the Mirror API. 4 | 5 | dependencies: 6 | browser: any 7 | google_plus_v1_api: any 8 | google_mirror_v1_api: 9 | path: ../../../discovery_api_dart_client_generator/output/dart_mirror_v1_api_client 10 | hangouts_api: 11 | git: git://github.com/Scarygami/dart_hangouts_api.git 12 | -------------------------------------------------------------------------------- /examples/hangout_companion/pubspec.yaml.test: -------------------------------------------------------------------------------- 1 | name: hangout_companion 2 | version: 0.0.1-dev 3 | description: An example of a Hangout application that accesses the Mirror API. 4 | 5 | dependencies: 6 | browser: any 7 | google_plus_v1_api: any 8 | google_mirror_v1_api: 9 | path: ../../../discovery_api_dart_client_generator/output/dart_mirror_v1_api_client 10 | hangouts_api: 11 | git: git://github.com/Scarygami/dart_hangouts_api.git 12 | -------------------------------------------------------------------------------- /mirror_api_server/client_secrets.json: -------------------------------------------------------------------------------- 1 | { 2 | "web": { 3 | "client_id": "YOUR_CLIENT_ID", 4 | "client_secret": "YOUR_CLIENT_SECRET", 5 | "api_key": "YOUR_API_KEY", 6 | "session_secret": "RANDOM_SESSION_SECRET", 7 | "additional_client_ids": ["CLIENT_ID1", "CLIENT_ID2"], 8 | "auth_uri": "https://accounts.google.com/o/oauth2/auth", 9 | "token_uri": "https://accounts.google.com/o/oauth2/token", 10 | "redirect_uris": ["postmessage"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /mirror_api_server/emulator/static/inner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Glass 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/client_secrets.json: -------------------------------------------------------------------------------- 1 | { 2 | "web": { 3 | "client_id": "YOUR_CLIENT_ID", 4 | "client_secret": "YOUR_CLIENT_SECRET", 5 | "api_key": "YOUR_API_KEY", 6 | "session_secret": "RANDOM_SESSION_SECRET", 7 | "test_api_url": "https:/YOUR-API.appspot.com", 8 | "auth_uri": "https://accounts.google.com/o/oauth2/auth", 9 | "token_uri": "https://accounts.google.com/o/oauth2/token", 10 | "redirect_uris": ["postmessage"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/client_secrets.json: -------------------------------------------------------------------------------- 1 | { 2 | "web": { 3 | "client_id": "YOUR_CLIENT_ID", 4 | "client_secret": "YOUR_CLIENT_SECRET", 5 | "api_key": "YOUR_API_KEY", 6 | "session_secret": "RANDOM_SESSION_SECRET", 7 | "test_api_url": "https:/YOUR-API.appspot.com", 8 | "auth_uri": "https://accounts.google.com/o/oauth2/auth", 9 | "token_uri": "https://accounts.google.com/o/oauth2/token", 10 | "redirect_uris": ["postmessage"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/app.yaml: -------------------------------------------------------------------------------- 1 | application: hangout-comment-tracker 2 | version: 1 3 | runtime: python27 4 | threadsafe: true 5 | api_version: 1 6 | 7 | handlers: 8 | 9 | - url: /images/*.png 10 | static_dir: static/images 11 | mime_type: image/png 12 | 13 | - url: /images 14 | static_dir: static/images 15 | 16 | - url: /(.*\.(html|css|js)) 17 | static_files: static/\1 18 | upload: static/(.*\.(html|css|js)) 19 | 20 | - url: .* 21 | script: main.app 22 | 23 | libraries: 24 | 25 | - name: jinja2 26 | version: latest 27 | -------------------------------------------------------------------------------- /mirror_api_server/emulator/static/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Glass 6 | 7 | 8 | 9 | 10 | 11 |
12 | 17 | 18 | -------------------------------------------------------------------------------- /mirror_api_server/emulator/static/grammar.grxml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | 11 | 12 | 13 | 14 | ok glass 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /mirror_api_server/index.yaml: -------------------------------------------------------------------------------- 1 | indexes: 2 | 3 | - kind: TimelineItem 4 | properties: 5 | - name: isDeleted 6 | - name: updated 7 | direction: desc 8 | 9 | - kind: TimelineItem 10 | properties: 11 | - name: user 12 | - name: updated 13 | direction: desc 14 | 15 | - kind: TimelineItem 16 | properties: 17 | - name: bundleId 18 | - name: updated 19 | direction: desc 20 | 21 | - kind: TimelineItem 22 | properties: 23 | - name: sourceItemId 24 | - name: updated 25 | direction: desc 26 | 27 | - kind: Location 28 | properties: 29 | - name: user 30 | - name: timestamp 31 | direction: desc 32 | 33 | - kind: TimelineItem 34 | properties: 35 | - name: isPinned 36 | - name: updated 37 | direction: desc -------------------------------------------------------------------------------- /examples/colours-of-the-world/app.yaml: -------------------------------------------------------------------------------- 1 | application: colours-of-the-world 2 | version: 1 3 | runtime: python27 4 | threadsafe: true 5 | api_version: 1 6 | 7 | handlers: 8 | 9 | - url: /images/*.png 10 | static_dir: static/images 11 | mime_type: image/png 12 | 13 | - url: /images 14 | static_dir: static/images 15 | 16 | - url: /(.*\.(html|css|js)) 17 | static_files: static/\1 18 | upload: static/(.*\.(html|css|js)) 19 | 20 | - url: /tasks/.* 21 | script: main.app 22 | login: admin 23 | 24 | - url: .* 25 | script: main.app 26 | 27 | 28 | builtins: 29 | 30 | - deferred: on 31 | 32 | 33 | libraries: 34 | 35 | - name: jinja2 36 | version: latest 37 | 38 | - name: PIL 39 | version: latest 40 | 41 | - name: numpy 42 | version: latest 43 | -------------------------------------------------------------------------------- /mirror_api_server/mirror_api/api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2013 Gerwin Sturm, FoldedSoft e.U. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0# 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | from google.appengine.ext import endpoints 17 | from mirror_api import MirrorApi 18 | 19 | ApiServer = endpoints.api_server([MirrorApi], restricted=False) 20 | -------------------------------------------------------------------------------- /utils/post-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | hooks_dir = os.path.dirname(os.path.abspath(__file__)) 6 | relative_dir = os.path.join(hooks_dir, '../..') 7 | project_root = os.path.abspath(relative_dir) 8 | 9 | paths = ['mirror_api_server/', 'examples/hangout-comment-tracker/', 'examples/colours-of-the-world/'] 10 | 11 | for path in paths: 12 | 13 | git_included_config = os.path.join(project_root, path + 'client_secrets.json') 14 | confidential_config = os.path.join(project_root, path + '_client_secrets.json') 15 | 16 | with open(git_included_config, 'rU') as fh: 17 | git_included_contents = fh.read() 18 | 19 | with open(confidential_config, 'rU') as fh: 20 | confidential_contents = fh.read() 21 | 22 | with open(git_included_config, 'w') as fh: 23 | fh.write(confidential_contents) 24 | 25 | with open(confidential_config, 'w') as fh: 26 | fh.write(git_included_contents) 27 | -------------------------------------------------------------------------------- /mirror_api_server/demos/templates/place.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% if chk_place %}{{ place.name }}{% else %}{% if chk_error %}Error{% else %}Nothing{% endif %}{% endif %} 6 | 7 | 8 | {% if chk_place %} 9 |
10 |

{{ place.name }}

11 | Location: 12 | 13 | Latitude {{ place.geometry.location.lat }} / 14 | Longitude {{ place.geometry.location.lng }} 15 |

16 | URL: 17 |
18 | {% else %} 19 | {% if chk_error %} 20 | Error: {{ error }} 21 | {% else %} 22 | Nothing to see here 23 | {% endif %} 24 | {% endif %} 25 | 26 | -------------------------------------------------------------------------------- /mirror_api_server/app.yaml: -------------------------------------------------------------------------------- 1 | application: mirror-api 2 | version: 1 3 | runtime: python27 4 | threadsafe: true 5 | api_version: 1 6 | 7 | handlers: 8 | 9 | - url: /glass/(.*\.(html|css|js|grxml)) 10 | static_files: emulator/static/\1 11 | upload: emulator/static/(.*\.(html|css|js|grxml)) 12 | 13 | - url: /glass/images/*.png 14 | static_dir: emulator/static/images 15 | mime_type: image/png 16 | 17 | - url: /glass/images 18 | static_dir: emulator/static/images 19 | 20 | - url: /_ah/spi/.* 21 | script: mirror_api.api.ApiServer 22 | 23 | - url: /images/*.png 24 | static_dir: service/static/images 25 | mime_type: image/png 26 | 27 | - url: /images 28 | static_dir: service/static/images 29 | 30 | - url: /service/(.*\.(html|css|js)) 31 | static_files: service/static/\1 32 | upload: service/static/(.*\.(html|css|js)) 33 | 34 | - url: /upload/.* 35 | script: mirror_api.upload.app 36 | 37 | - url: .* 38 | script: main.app 39 | 40 | libraries: 41 | - name: pycrypto 42 | version: latest 43 | 44 | - name: jinja2 45 | version: latest 46 | 47 | - name: PIL 48 | version: latest 49 | -------------------------------------------------------------------------------- /mirror_api_server/lib/oauth2client/anyjson.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Utility module to import a JSON module 16 | 17 | Hides all the messy details of exactly where 18 | we get a simplejson module from. 19 | """ 20 | 21 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 22 | 23 | 24 | try: # pragma: no cover 25 | # Should work for Python2.6 and higher. 26 | import json as simplejson 27 | except ImportError: # pragma: no cover 28 | try: 29 | import simplejson 30 | except ImportError: 31 | # Try to import from django, should work on App Engine 32 | from django.utils import simplejson 33 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/lib/oauth2client/anyjson.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Utility module to import a JSON module 16 | 17 | Hides all the messy details of exactly where 18 | we get a simplejson module from. 19 | """ 20 | 21 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 22 | 23 | 24 | try: # pragma: no cover 25 | # Should work for Python2.6 and higher. 26 | import json as simplejson 27 | except ImportError: # pragma: no cover 28 | try: 29 | import simplejson 30 | except ImportError: 31 | # Try to import from django, should work on App Engine 32 | from django.utils import simplejson 33 | -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/lib/oauth2client/anyjson.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Utility module to import a JSON module 16 | 17 | Hides all the messy details of exactly where 18 | we get a simplejson module from. 19 | """ 20 | 21 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 22 | 23 | 24 | try: # pragma: no cover 25 | # Should work for Python2.6 and higher. 26 | import json as simplejson 27 | except ImportError: # pragma: no cover 28 | try: 29 | import simplejson 30 | except ImportError: 31 | # Try to import from django, should work on App Engine 32 | from django.utils import simplejson 33 | -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2013 Gerwin Sturm, FoldedSoft e.U. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0# 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """RequestHandlers for Hangout Comment Tracker Glassware""" 17 | 18 | __author__ = 'scarygami@gmail.com (Gerwin Sturm)' 19 | 20 | # Add the library location to the path 21 | import sys 22 | sys.path.insert(0, 'lib') 23 | 24 | from utils import config 25 | import webapp2 26 | 27 | from auth import AUTH_ROUTES 28 | from notify import NOTIFY_ROUTES 29 | from service import SERVICE_ROUTES 30 | 31 | ROUTES = (AUTH_ROUTES + SERVICE_ROUTES + NOTIFY_ROUTES ) 32 | 33 | app = webapp2.WSGIApplication(ROUTES, debug=True, config=config) 34 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2013 Gerwin Sturm, FoldedSoft e.U. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0# 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """RequestHandlers for Hangout Comment Tracker Glassware""" 17 | 18 | __author__ = 'scarygami@gmail.com (Gerwin Sturm)' 19 | 20 | # Add the library location to the path 21 | import sys 22 | sys.path.insert(0, 'lib') 23 | 24 | from utils import config 25 | import webapp2 26 | 27 | from auth import AUTH_ROUTES 28 | from notify import NOTIFY_ROUTES 29 | from service import SERVICE_ROUTES 30 | from tasks import TASK_ROUTES 31 | 32 | ROUTES = (AUTH_ROUTES + SERVICE_ROUTES + NOTIFY_ROUTES + TASK_ROUTES) 33 | 34 | app = webapp2.WSGIApplication(ROUTES, debug=True, config=config) 35 | -------------------------------------------------------------------------------- /mirror_api_server/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2013 Gerwin Sturm, FoldedSoft e.U. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0# 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """RequestHandlers for Glass emulator and Demo services""" 17 | 18 | __author__ = 'scarygami@gmail.com (Gerwin Sturm)' 19 | 20 | # Add the library location to the path 21 | import sys 22 | sys.path.insert(0, 'lib') 23 | 24 | from utils import config 25 | import webapp2 26 | 27 | from service.auth import AUTH_ROUTES 28 | from service.notify import NOTIFY_ROUTES 29 | from service.service import SERVICE_ROUTES 30 | from demos import DEMO_ROUTES 31 | 32 | ROUTES = (AUTH_ROUTES + SERVICE_ROUTES + NOTIFY_ROUTES + DEMO_ROUTES) 33 | 34 | # Remove the next two lines if you don't want to host a Glass emulator 35 | from emulator.glass import GLASS_ROUTES 36 | ROUTES = (ROUTES + GLASS_ROUTES) 37 | 38 | app = webapp2.WSGIApplication(ROUTES, debug=True, config=config) 39 | -------------------------------------------------------------------------------- /examples/hangout_companion/web/hangout_companion.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | Hangout Companion 28 | 29 | 30 | 31 | 32 |

Hangout Companion

33 | 34 | 35 | 36 | 37 | 38 | ]]> 39 |
40 |
41 | 42 | -------------------------------------------------------------------------------- /mirror_api_server/service/static/service.css: -------------------------------------------------------------------------------- 1 | * { 2 | -moz-box-sizing: border-box; 3 | box-sizing: border-box; 4 | padding: 0; 5 | margin: 0; 6 | } 7 | 8 | body { 9 | padding: 5px; 10 | } 11 | 12 | #signin, #signout { 13 | padding: 5px; 14 | } 15 | 16 | #signout_button, #send_card { 17 | display: inline-block; 18 | text-align: left; 19 | padding: 5px; 20 | cursor: pointer; 21 | line-height: 20px; 22 | border-radius: 4px; 23 | margin-top: 5px; 24 | font-family: Arial, sans-serif; 25 | font-size: 13px; 26 | background: #CCC; 27 | border: 1px solid #999; 28 | color: #333; 29 | } 30 | 31 | #glass input, #glass textarea { 32 | text-align: left; 33 | padding: 5px; 34 | line-height: 20px; 35 | border-radius: 4px; 36 | margin-top: 5px; 37 | font-family: Arial, sans-serif; 38 | font-size: 15px; 39 | background: white; 40 | border: 1px solid #999; 41 | color: black; 42 | width: 500px; 43 | } 44 | 45 | #timeline { 46 | width: 100%; 47 | max-width: 100%; 48 | min-width: 100%; 49 | overflow-y: hidden; 50 | overflow-x: scroll; 51 | white-space: nowrap; 52 | } 53 | 54 | .card { 55 | display: inline-block; 56 | position: relative; 57 | width: 640px; 58 | height: 600px; 59 | margin-right: 10px; 60 | } 61 | 62 | .card_iframe { 63 | background-color: black; 64 | border: 0px none transparent; 65 | padding: 0px; 66 | overflow: hidden; 67 | position: absolute; 68 | height: 360px; width: 640px; 69 | left: 0; top: 0; 70 | } 71 | 72 | .card_metadata { 73 | position: absolute; 74 | background-color: #EEE; 75 | display: block; 76 | padding: 5px; 77 | width: 640px; 78 | max-width: 640px; 79 | height: 240px; 80 | max-height: 240px; 81 | overflow: auto; 82 | left: 0; top: 360px; 83 | } 84 | -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/static/service.css: -------------------------------------------------------------------------------- 1 | * { 2 | -moz-box-sizing: border-box; 3 | box-sizing: border-box; 4 | padding: 0; 5 | margin: 0; 6 | } 7 | 8 | body { 9 | padding: 5px; 10 | } 11 | 12 | #signin, #signout, #glass { 13 | padding: 5px; 14 | } 15 | 16 | button { 17 | display: inline-block; 18 | text-align: left; 19 | padding: 5px; 20 | cursor: pointer; 21 | line-height: 20px; 22 | border-radius: 4px; 23 | margin-top: 5px; 24 | font-family: Arial, sans-serif; 25 | font-size: 13px; 26 | min-width: 80px; 27 | text-align: center; 28 | } 29 | 30 | #signout_button { 31 | background: #CCC; 32 | border: 1px solid #999; 33 | color: #333; 34 | } 35 | 36 | #ct-add { 37 | background: #4d90fe; 38 | border: 1px solid #3079ed; 39 | color: white; 40 | } 41 | 42 | input { 43 | text-align: left; 44 | padding: 5px; 45 | line-height: 20px; 46 | border-radius: 4px; 47 | margin-top: 5px; 48 | font-family: Arial, sans-serif; 49 | font-size: 15px; 50 | background: white; 51 | border: 1px solid #999; 52 | color: black; 53 | width: 500px; 54 | } 55 | 56 | #glass { 57 | width: 100%; 58 | min-width: 1000px; 59 | } 60 | 61 | .sources { 62 | width: 450px; 63 | float: left; 64 | } 65 | 66 | .list { 67 | width: 450px; 68 | padding: 5px; 69 | } 70 | 71 | #footer { 72 | text-align: right; 73 | font-size: 9pt; 74 | } 75 | 76 | .ct-post { border-bottom: 1px dotted #CCC; vertical-align: top; padding: 12px 8px 7px 47px; word-wrap: break-word; position: relative;} 77 | .ct-post_pic {display: block; border: 0; max-width: 32px; max-height: 32px; float: left; margin-left: -40px;} 78 | .ct-service_pic {display: block; border: 0; max-width: 16px; max-height: 16px; float: left; margin-left: -20px; margin-top: 20px;} 79 | .ct-post_time {display: block; float: right; color: #999; padding: 0;} 80 | .ct-post_text {margin-top: 2px; padding: 0;} 81 | .ct-post_time a {color: #999;} -------------------------------------------------------------------------------- /mirror_api_server/emulator/templates/glass.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Glass 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 20 |
21 | 24 | 25 | 28 | 40 | 41 | -------------------------------------------------------------------------------- /mirror_api_server/service/templates/service.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mirror API Service 6 | 7 | 8 | 9 | 10 |
11 | 22 |
23 | 26 |


30 | 33 | 44 | 45 | -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/templates/service.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hangout Comment Tracker - Glassware 6 | 7 | 8 | 9 | 10 |
11 | 21 |
22 | 25 |


37 | 41 | 52 | 53 | -------------------------------------------------------------------------------- /utils/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | 6 | hooks_dir = os.path.dirname(os.path.abspath(__file__)) 7 | relative_dir = os.path.join(hooks_dir, '../..') 8 | project_root = os.path.abspath(relative_dir) 9 | 10 | paths = ['mirror_api_server/', 'examples/hangout-comment-tracker/', 'examples/colours-of-the-world/'] 11 | 12 | for path in paths: 13 | 14 | git_included_config = os.path.join(project_root, path + 'client_secrets.json') 15 | confidential_config = os.path.join(project_root, path + '_client_secrets.json') 16 | 17 | with open(git_included_config, 'rU') as fh: 18 | git_included_contents = fh.read() 19 | 20 | with open(confidential_config, 'rU') as fh: 21 | confidential_contents = fh.read() 22 | 23 | with open(git_included_config, 'w') as fh: 24 | fh.write(confidential_contents) 25 | 26 | with open(confidential_config, 'w') as fh: 27 | fh.write(git_included_contents) 28 | 29 | os.system('git add %s' % git_included_config) 30 | 31 | # In the rare case, that config.js is the only file changed, 32 | # this switch will render the commit worthless (since it will 33 | # move the repo into an unchanged state. In this case, we 34 | # revert the changes before exiting. 35 | 36 | with os.popen('git st') as fh: 37 | git_status = fh.read() 38 | 39 | if 'nothing to commit' in git_status or 'no changes added to commit' in git_status or 'nothing added to commit' in git_status: 40 | msg = '# From pre-commit hook: No commit necessary, ' \ 41 | 'sensitive config unchanged. #' 42 | hash_head = '#' * len(msg) 43 | print ('%s\n%s\n%s\n\n' % (hash_head, msg, hash_head)), 44 | 45 | for path in paths: 46 | 47 | git_included_config = os.path.join(project_root, path + 'client_secrets.json') 48 | confidential_config = os.path.join(project_root, path + '_client_secrets.json') 49 | 50 | with open(git_included_config, 'rU') as fh: 51 | git_included_contents = fh.read() 52 | 53 | with open(confidential_config, 'rU') as fh: 54 | confidential_contents = fh.read() 55 | 56 | with open(git_included_config, 'w') as fh: 57 | fh.write(confidential_contents) 58 | 59 | with open(confidential_config, 'w') as fh: 60 | fh.write(git_included_contents) 61 | 62 | sys.exit(1) 63 | -------------------------------------------------------------------------------- /mirror_api_server/demos/friend_finder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2013 Gerwin Sturm, FoldedSoft e.U. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0# 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Methods for Friend finder service""" 17 | 18 | __author__ = 'scarygami@gmail.com (Gerwin Sturm)' 19 | 20 | __all__ = ["handle_location", "WELCOMES"] 21 | 22 | 23 | """Welcome message cards that are sent when the user first connects to this service""" 24 | WELCOMES = [ 25 | { 26 | "html": ("
" 27 | " " 28 | "
" 29 | "
" 30 | "

Welcome to Friend Finder

" 31 | "
" 32 | "
") 33 | } 34 | ] 35 | 36 | 37 | def handle_location(item, notification, service, test): 38 | """Callback for Location updates.""" 39 | 40 | """ 41 | Card layout for cover: 42 |
43 | 44 |
45 | 46 |
47 | 48 | Card layout for detailed card: 49 |
50 |
51 | 52 |

Gerwin Sturm

53 |
54 |
55 | 56 |
57 |
58 | """ 59 | 60 | # TODO: implement 61 | 62 | return 63 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/models.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2013 Gerwin Sturm, FoldedSoft e.U. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0# 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Datastore models for comment tracker""" 17 | 18 | __author__ = 'scarygami@gmail.com (Gerwin Sturm)' 19 | 20 | from google.appengine.ext import ndb 21 | from oauth2client.appengine import CredentialsNDBProperty 22 | 23 | 24 | class User(ndb.Model): 25 | """ 26 | Datastore model to keep all relevant information about a user 27 | 28 | Properties: 29 | displayName Name of the user as returned by the Google+ API 30 | imageUrl Avatar image of the user as returned by the Google+ API 31 | verifyToken Random token generated for each user to check validity of incoming notifications 32 | credentials OAuth2 Access and refresh token to be used for requests against the Mirror API 33 | sources List of tracked sources 34 | currentTask Colour 35 | """ 36 | 37 | displayName = ndb.StringProperty() 38 | imageUrl = ndb.StringProperty() 39 | verifyToken = ndb.StringProperty() 40 | credentials = CredentialsNDBProperty() 41 | currentTask = ndb.StringProperty() 42 | 43 | 44 | class TestUser(User): 45 | """Separate datastore model to keep credentials for test and real environment separate""" 46 | 47 | _testUser = True 48 | 49 | 50 | class Submission(ndb.Model): 51 | """ 52 | Datastore model for a submission for one of the tasks 53 | 54 | Properties: 55 | colour Colour for which the submission was meant 56 | hue Hue value of the colour for sorting 57 | blobkey Reference to the image in blobstore 58 | url Public serving url for the image in blobstore 59 | date Submission date 60 | """ 61 | 62 | colour = ndb.StringProperty() 63 | hue = ndb.IntegerProperty() 64 | blobkey = ndb.BlobKeyProperty() 65 | url = ndb.StringProperty() 66 | date = ndb.DateTimeProperty(auto_now_add=True) 67 | -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/notify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2013 Gerwin Sturm, FoldedSoft e.U. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0# 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """ 17 | Notification/subscription handler 18 | 19 | Handles subscription post requests coming from the Mirror API and forwards 20 | the requests to the relevant demo services. 21 | 22 | """ 23 | 24 | __author__ = 'scarygami@gmail.com (Gerwin Sturm)' 25 | 26 | import utils 27 | from auth import get_auth_service 28 | 29 | import json 30 | import logging 31 | from datetime import datetime 32 | from google.appengine.ext import ndb 33 | 34 | 35 | class TimelineNotifyHandler(utils.BaseHandler): 36 | """ 37 | Handles all timeline notifications (updates, deletes, inserts) 38 | Forwards the information to implemented demo services 39 | """ 40 | 41 | def post(self, test): 42 | """Callback for Timeline updates.""" 43 | 44 | message = self.request.body 45 | data = json.loads(message) 46 | logging.info(data) 47 | 48 | self.response.status = 200 49 | 50 | gplus_id = data["userToken"] 51 | verifyToken = data["verifyToken"] 52 | if test is not None: 53 | user = ndb.Key("TestUser", gplus_id).get() 54 | else: 55 | user = ndb.Key("User", gplus_id).get() 56 | if user is None or user.verifyToken != verifyToken: 57 | logging.info("Wrong user") 58 | return 59 | 60 | if data["collection"] != "timeline": 61 | logging.info("Wrong collection") 62 | return 63 | 64 | service = get_auth_service(gplus_id, test) 65 | 66 | if service is None: 67 | logging.info("No valid credentials") 68 | return 69 | 70 | result = service.timeline().get(id=data["itemId"]).execute() 71 | logging.info(result) 72 | 73 | #for demo_service in demo_services: 74 | # if hasattr(demo_service, "handle_item"): 75 | # demo_service.handle_item(result, data, service, test) 76 | 77 | 78 | NOTIFY_ROUTES = [ 79 | (r"(/test)?/timeline_update", TimelineNotifyHandler) 80 | ] 81 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/notify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2013 Gerwin Sturm, FoldedSoft e.U. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0# 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """ 17 | Notification/subscription handler 18 | 19 | Handles subscription post requests coming from the Mirror API 20 | 21 | """ 22 | 23 | __author__ = 'scarygami@gmail.com (Gerwin Sturm)' 24 | 25 | import utils 26 | from auth import get_auth_service 27 | 28 | import json 29 | import logging 30 | from datetime import datetime 31 | from google.appengine.api import taskqueue 32 | from google.appengine.ext import ndb 33 | 34 | 35 | class TimelineNotifyHandler(utils.BaseHandler): 36 | """ 37 | Handles all timeline notifications (updates, deletes, inserts) 38 | Forwards the information to implemented demo services 39 | """ 40 | 41 | def post(self, test): 42 | """Callback for Timeline updates.""" 43 | 44 | message = self.request.body 45 | data = json.loads(message) 46 | logging.info(data) 47 | 48 | self.response.status = 200 49 | 50 | gplus_id = data["userToken"] 51 | verifyToken = data["verifyToken"] 52 | if test is not None: 53 | user = ndb.Key("TestUser", gplus_id).get() 54 | else: 55 | user = ndb.Key("User", gplus_id).get() 56 | if user is None: 57 | logging.info("Wrong user") 58 | return 59 | if user.verifyToken != verifyToken: 60 | logging.info("verifyToken mismatch") 61 | return 62 | 63 | if data["collection"] != "timeline": 64 | logging.info("Wrong collection") 65 | return 66 | 67 | share = False 68 | if "userActions" in data: 69 | for action in data["userActions"]: 70 | if "type" in action and action["type"] == "SHARE": 71 | share = True 72 | break 73 | 74 | if share: 75 | # Evaluate submission 76 | taskqueue.add(url="/tasks/evaluate", 77 | params={"user": gplus_id, "test": test, "item": data["itemId"]}, 78 | method="POST") 79 | 80 | 81 | NOTIFY_ROUTES = [ 82 | (r"(/test)?/timeline_update", TimelineNotifyHandler) 83 | ] 84 | -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/models.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2013 Gerwin Sturm, FoldedSoft e.U. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0# 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Datastore models for comment tracker""" 17 | 18 | __author__ = 'scarygami@gmail.com (Gerwin Sturm)' 19 | 20 | from google.appengine.ext import ndb 21 | from oauth2client.appengine import CredentialsNDBProperty 22 | 23 | PLUS_POST = 1 24 | YT_POST = 2 25 | PLUS_SEARCH = 3 26 | 27 | 28 | class Author(ndb.Model): 29 | """Datastore model for the author of a post or comment""" 30 | author = ndb.StringProperty() 31 | author_url = ndb.StringProperty() 32 | author_image = ndb.StringProperty() 33 | 34 | 35 | class Comment(ndb.Model): 36 | """Datastore model for a single comment or search result""" 37 | 38 | id = ndb.StringProperty() 39 | author = ndb.LocalStructuredProperty(Author) 40 | posted = ndb.DateTimeProperty() 41 | link = ndb.StringProperty() 42 | content = ndb.TextProperty() 43 | 44 | 45 | class Source(ndb.Model): 46 | """ 47 | Datastore model to keep track of all sources currently being tracked 48 | This will contain the sources for all users to prevent duplicate 49 | requests in case serveral people are tracking the same source. 50 | 51 | When all users remove a source `active` will be set to False to prevent 52 | unnecessary tracking, but the Source will be kept with its comments 53 | so people can resume tracking at a later point 54 | """ 55 | id = ndb.StringProperty() 56 | type = ndb.IntegerProperty() 57 | author = ndb.LocalStructuredProperty(Author) 58 | posted = ndb.DateTimeProperty() 59 | link = ndb.StringProperty() 60 | content = ndb.TextProperty() 61 | comments = ndb.LocalStructuredProperty(Comment, repeated=True) 62 | active = ndb.BooleanProperty() 63 | 64 | 65 | class User(ndb.Model): 66 | """ 67 | Datastore model to keep all relevant information about a user 68 | 69 | Properties: 70 | displayName Name of the user as returned by the Google+ API 71 | imageUrl Avatar image of the user as returned by the Google+ API 72 | verifyToken Random token generated for each user to check validity of incoming notifications 73 | credentials OAuth2 Access and refresh token to be used for requests against the Mirror API 74 | sources List of tracked sources 75 | """ 76 | 77 | displayName = ndb.StringProperty() 78 | imageUrl = ndb.StringProperty() 79 | verifyToken = ndb.StringProperty() 80 | credentials = CredentialsNDBProperty() 81 | sources = ndb.KeyProperty(kind=Source, repeated=True) 82 | 83 | 84 | class TestUser(User): 85 | """Separate datastore model to keep credentials for test and real environment separate""" 86 | 87 | _testUser = True 88 | -------------------------------------------------------------------------------- /mirror_api_server/lib/oauth2client/gce.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2012 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Utilities for Google Compute Engine 16 | 17 | Utilities for making it easier to use OAuth 2.0 on Google Compute Engine. 18 | """ 19 | 20 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 21 | 22 | import httplib2 23 | import logging 24 | import uritemplate 25 | 26 | from oauth2client import util 27 | from oauth2client.anyjson import simplejson 28 | from oauth2client.client import AccessTokenRefreshError 29 | from oauth2client.client import AssertionCredentials 30 | 31 | logger = logging.getLogger(__name__) 32 | 33 | # URI Template for the endpoint that returns access_tokens. 34 | META = ('http://metadata.google.internal/0.1/meta-data/service-accounts/' 35 | 'default/acquire{?scope}') 36 | 37 | 38 | class AppAssertionCredentials(AssertionCredentials): 39 | """Credentials object for Compute Engine Assertion Grants 40 | 41 | This object will allow a Compute Engine instance to identify itself to 42 | Google and other OAuth 2.0 servers that can verify assertions. It can be used 43 | for the purpose of accessing data stored under an account assigned to the 44 | Compute Engine instance itself. 45 | 46 | This credential does not require a flow to instantiate because it represents 47 | a two legged flow, and therefore has all of the required information to 48 | generate and refresh its own access tokens. 49 | """ 50 | 51 | @util.positional(2) 52 | def __init__(self, scope, **kwargs): 53 | """Constructor for AppAssertionCredentials 54 | 55 | Args: 56 | scope: string or iterable of strings, scope(s) of the credentials being 57 | requested. 58 | """ 59 | self.scope = util.scopes_to_string(scope) 60 | 61 | # Assertion type is no longer used, but still in the parent class signature. 62 | super(AppAssertionCredentials, self).__init__(None) 63 | 64 | @classmethod 65 | def from_json(cls, json): 66 | data = simplejson.loads(json) 67 | return AppAssertionCredentials(data['scope']) 68 | 69 | def _refresh(self, http_request): 70 | """Refreshes the access_token. 71 | 72 | Skip all the storage hoops and just refresh using the API. 73 | 74 | Args: 75 | http_request: callable, a callable that matches the method signature of 76 | httplib2.Http.request, used to make the refresh request. 77 | 78 | Raises: 79 | AccessTokenRefreshError: When the refresh fails. 80 | """ 81 | uri = uritemplate.expand(META, {'scope': self.scope}) 82 | response, content = http_request(uri) 83 | if response.status == 200: 84 | try: 85 | d = simplejson.loads(content) 86 | except StandardError, e: 87 | raise AccessTokenRefreshError(str(e)) 88 | self.access_token = d['accessToken'] 89 | else: 90 | raise AccessTokenRefreshError(content) 91 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/lib/oauth2client/gce.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2012 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Utilities for Google Compute Engine 16 | 17 | Utilities for making it easier to use OAuth 2.0 on Google Compute Engine. 18 | """ 19 | 20 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 21 | 22 | import httplib2 23 | import logging 24 | import uritemplate 25 | 26 | from oauth2client import util 27 | from oauth2client.anyjson import simplejson 28 | from oauth2client.client import AccessTokenRefreshError 29 | from oauth2client.client import AssertionCredentials 30 | 31 | logger = logging.getLogger(__name__) 32 | 33 | # URI Template for the endpoint that returns access_tokens. 34 | META = ('http://metadata.google.internal/0.1/meta-data/service-accounts/' 35 | 'default/acquire{?scope}') 36 | 37 | 38 | class AppAssertionCredentials(AssertionCredentials): 39 | """Credentials object for Compute Engine Assertion Grants 40 | 41 | This object will allow a Compute Engine instance to identify itself to 42 | Google and other OAuth 2.0 servers that can verify assertions. It can be used 43 | for the purpose of accessing data stored under an account assigned to the 44 | Compute Engine instance itself. 45 | 46 | This credential does not require a flow to instantiate because it represents 47 | a two legged flow, and therefore has all of the required information to 48 | generate and refresh its own access tokens. 49 | """ 50 | 51 | @util.positional(2) 52 | def __init__(self, scope, **kwargs): 53 | """Constructor for AppAssertionCredentials 54 | 55 | Args: 56 | scope: string or iterable of strings, scope(s) of the credentials being 57 | requested. 58 | """ 59 | self.scope = util.scopes_to_string(scope) 60 | 61 | # Assertion type is no longer used, but still in the parent class signature. 62 | super(AppAssertionCredentials, self).__init__(None) 63 | 64 | @classmethod 65 | def from_json(cls, json): 66 | data = simplejson.loads(json) 67 | return AppAssertionCredentials(data['scope']) 68 | 69 | def _refresh(self, http_request): 70 | """Refreshes the access_token. 71 | 72 | Skip all the storage hoops and just refresh using the API. 73 | 74 | Args: 75 | http_request: callable, a callable that matches the method signature of 76 | httplib2.Http.request, used to make the refresh request. 77 | 78 | Raises: 79 | AccessTokenRefreshError: When the refresh fails. 80 | """ 81 | uri = uritemplate.expand(META, {'scope': self.scope}) 82 | response, content = http_request(uri) 83 | if response.status == 200: 84 | try: 85 | d = simplejson.loads(content) 86 | except StandardError, e: 87 | raise AccessTokenRefreshError(str(e)) 88 | self.access_token = d['accessToken'] 89 | else: 90 | raise AccessTokenRefreshError(content) 91 | -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/lib/oauth2client/gce.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2012 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Utilities for Google Compute Engine 16 | 17 | Utilities for making it easier to use OAuth 2.0 on Google Compute Engine. 18 | """ 19 | 20 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 21 | 22 | import httplib2 23 | import logging 24 | import uritemplate 25 | 26 | from oauth2client import util 27 | from oauth2client.anyjson import simplejson 28 | from oauth2client.client import AccessTokenRefreshError 29 | from oauth2client.client import AssertionCredentials 30 | 31 | logger = logging.getLogger(__name__) 32 | 33 | # URI Template for the endpoint that returns access_tokens. 34 | META = ('http://metadata.google.internal/0.1/meta-data/service-accounts/' 35 | 'default/acquire{?scope}') 36 | 37 | 38 | class AppAssertionCredentials(AssertionCredentials): 39 | """Credentials object for Compute Engine Assertion Grants 40 | 41 | This object will allow a Compute Engine instance to identify itself to 42 | Google and other OAuth 2.0 servers that can verify assertions. It can be used 43 | for the purpose of accessing data stored under an account assigned to the 44 | Compute Engine instance itself. 45 | 46 | This credential does not require a flow to instantiate because it represents 47 | a two legged flow, and therefore has all of the required information to 48 | generate and refresh its own access tokens. 49 | """ 50 | 51 | @util.positional(2) 52 | def __init__(self, scope, **kwargs): 53 | """Constructor for AppAssertionCredentials 54 | 55 | Args: 56 | scope: string or iterable of strings, scope(s) of the credentials being 57 | requested. 58 | """ 59 | self.scope = util.scopes_to_string(scope) 60 | 61 | # Assertion type is no longer used, but still in the parent class signature. 62 | super(AppAssertionCredentials, self).__init__(None) 63 | 64 | @classmethod 65 | def from_json(cls, json): 66 | data = simplejson.loads(json) 67 | return AppAssertionCredentials(data['scope']) 68 | 69 | def _refresh(self, http_request): 70 | """Refreshes the access_token. 71 | 72 | Skip all the storage hoops and just refresh using the API. 73 | 74 | Args: 75 | http_request: callable, a callable that matches the method signature of 76 | httplib2.Http.request, used to make the refresh request. 77 | 78 | Raises: 79 | AccessTokenRefreshError: When the refresh fails. 80 | """ 81 | uri = uritemplate.expand(META, {'scope': self.scope}) 82 | response, content = http_request(uri) 83 | if response.status == 200: 84 | try: 85 | d = simplejson.loads(content) 86 | except StandardError, e: 87 | raise AccessTokenRefreshError(str(e)) 88 | self.access_token = d['accessToken'] 89 | else: 90 | raise AccessTokenRefreshError(content) 91 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/templates/service.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Colours of the World 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 |
16 | 26 |
27 |

Sign in to connect with your Glass™ device and
start seeing the world in different colours.

28 |
29 | 45 |
46 | 54 | 64 | 65 | 76 | 77 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2013 Gerwin Sturm, FoldedSoft e.U. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0# 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Configuration options and helper functions for all services""" 17 | 18 | __author__ = 'scarygami@gmail.com (Gerwin Sturm)' 19 | 20 | import jinja2 21 | import json 22 | import os 23 | import webapp2 24 | 25 | from apiclient.discovery import build 26 | from google.appengine.api.app_identity import get_application_id 27 | from webapp2_extras import sessions 28 | from webapp2_extras.appengine import sessions_memcache 29 | 30 | JINJA = jinja2.Environment(loader=jinja2.FileSystemLoader(os.path.dirname(__file__))) 31 | 32 | with open("client_secrets.json", "r") as fh: 33 | secrets = json.load(fh)["web"] 34 | CLIENT_ID = secrets["client_id"] 35 | SESSION_KEY = str(secrets["session_secret"]) 36 | API_KEY = secrets["api_key"] 37 | test_api_url = secrets["test_api_url"] 38 | discovery_service_url = test_api_url + "/_ah/api/discovery/v1/apis/{api}/{apiVersion}/rest" 39 | 40 | appname = get_application_id() 41 | base_url = "https://" + appname + ".appspot.com" 42 | 43 | config = {} 44 | config["webapp2_extras.sessions"] = {"secret_key": SESSION_KEY} 45 | 46 | # Add any additional scopes that you might need for your service to access other Google APIs 47 | COMMON_SCOPES = [ 48 | "https://www.googleapis.com/auth/plus.login", 49 | "https://www.googleapis.com/auth/games" 50 | ] 51 | 52 | # userinfo.email scope is required to work with Google Cloud Endpoints 53 | TEST_SCOPES = ["https://www.googleapis.com/auth/userinfo.email"] 54 | 55 | REAL_SCOPES = [ 56 | "https://www.googleapis.com/auth/glass.timeline" 57 | ] 58 | 59 | 60 | def createError(code, message): 61 | """Create a JSON string to be returned as error response to requests""" 62 | return json.dumps({"error": {"code": code, "message": message}}) 63 | 64 | 65 | def createMessage(message): 66 | """Create a JSON string to be returned as response to requests""" 67 | return json.dumps({"message": message}) 68 | 69 | 70 | class BaseHandler(webapp2.RequestHandler): 71 | """Base request handler to enable session storage for all handlers""" 72 | 73 | def dispatch(self): 74 | # Get a session store for this request. 75 | self.session_store = sessions.get_store(request=self.request) 76 | 77 | try: 78 | # Dispatch the request. 79 | webapp2.RequestHandler.dispatch(self) 80 | finally: 81 | # Save all sessions. 82 | self.session_store.save_sessions(self.response) 83 | 84 | @webapp2.cached_property 85 | def session(self): 86 | return self.session_store.get_session(name='mirror_session', factory=sessions_memcache.MemcacheSessionFactory) 87 | 88 | 89 | def build_service_from_service(service, api, version): 90 | """Build a Google API service using another pre-authed service""" 91 | 92 | new_service = build(api, version, http=service._http) 93 | 94 | return new_service 95 | -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2013 Gerwin Sturm, FoldedSoft e.U. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0# 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Configuration options and helper functions for all services""" 17 | 18 | __author__ = 'scarygami@gmail.com (Gerwin Sturm)' 19 | 20 | import jinja2 21 | import json 22 | import os 23 | import webapp2 24 | 25 | from apiclient.discovery import build 26 | from google.appengine.api.app_identity import get_application_id 27 | from webapp2_extras import sessions 28 | from webapp2_extras.appengine import sessions_memcache 29 | 30 | JINJA = jinja2.Environment(loader=jinja2.FileSystemLoader(os.path.dirname(__file__))) 31 | 32 | with open("client_secrets.json", "r") as fh: 33 | secrets = json.load(fh)["web"] 34 | CLIENT_ID = secrets["client_id"] 35 | SESSION_KEY = str(secrets["session_secret"]) 36 | API_KEY = secrets["api_key"] 37 | test_api_url = secrets["test_api_url"] 38 | discovery_service_url = test_api_url + "/_ah/api/discovery/v1/apis/{api}/{apiVersion}/rest" 39 | 40 | appname = get_application_id() 41 | base_url = "https://" + appname + ".appspot.com" 42 | 43 | config = {} 44 | config["webapp2_extras.sessions"] = {"secret_key": SESSION_KEY} 45 | 46 | # Add any additional scopes that you might need for your service to access other Google APIs 47 | COMMON_SCOPES = [ 48 | "https://www.googleapis.com/auth/plus.login", 49 | "https://www.googleapis.com/auth/youtube.readonly" 50 | ] 51 | 52 | # userinfo.email scope is required to work with Google Cloud Endpoints 53 | TEST_SCOPES = ["https://www.googleapis.com/auth/userinfo.email"] 54 | 55 | REAL_SCOPES = [ 56 | "https://www.googleapis.com/auth/glass.timeline" 57 | ] 58 | 59 | 60 | def createError(code, message): 61 | """Create a JSON string to be returned as error response to requests""" 62 | return json.dumps({"error": {"code": code, "message": message}}) 63 | 64 | 65 | def createMessage(message): 66 | """Create a JSON string to be returned as response to requests""" 67 | return json.dumps({"message": message}) 68 | 69 | 70 | class BaseHandler(webapp2.RequestHandler): 71 | """Base request handler to enable session storage for all handlers""" 72 | 73 | def dispatch(self): 74 | # Get a session store for this request. 75 | self.session_store = sessions.get_store(request=self.request) 76 | 77 | try: 78 | # Dispatch the request. 79 | webapp2.RequestHandler.dispatch(self) 80 | finally: 81 | # Save all sessions. 82 | self.session_store.save_sessions(self.response) 83 | 84 | @webapp2.cached_property 85 | def session(self): 86 | return self.session_store.get_session(name='mirror_session', factory=sessions_memcache.MemcacheSessionFactory) 87 | 88 | 89 | def build_service_from_service(service, api, version): 90 | """Build a Google API service using another pre-authed service""" 91 | 92 | new_service = build(api, version, http=service._http) 93 | 94 | return new_service 95 | -------------------------------------------------------------------------------- /mirror_api_server/lib/oauth2client/keyring_storage.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2012 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """A keyring based Storage. 16 | 17 | A Storage for Credentials that uses the keyring module. 18 | """ 19 | 20 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 21 | 22 | import keyring 23 | import threading 24 | 25 | from client import Storage as BaseStorage 26 | from client import Credentials 27 | 28 | 29 | class Storage(BaseStorage): 30 | """Store and retrieve a single credential to and from the keyring. 31 | 32 | To use this module you must have the keyring module installed. See 33 | . This is an optional module and is not 34 | installed with oauth2client by default because it does not work on all the 35 | platforms that oauth2client supports, such as Google App Engine. 36 | 37 | The keyring module is a cross-platform 38 | library for access the keyring capabilities of the local system. The user will 39 | be prompted for their keyring password when this module is used, and the 40 | manner in which the user is prompted will vary per platform. 41 | 42 | Usage: 43 | from oauth2client.keyring_storage import Storage 44 | 45 | s = Storage('name_of_application', 'user1') 46 | credentials = s.get() 47 | 48 | """ 49 | 50 | def __init__(self, service_name, user_name): 51 | """Constructor. 52 | 53 | Args: 54 | service_name: string, The name of the service under which the credentials 55 | are stored. 56 | user_name: string, The name of the user to store credentials for. 57 | """ 58 | self._service_name = service_name 59 | self._user_name = user_name 60 | self._lock = threading.Lock() 61 | 62 | def acquire_lock(self): 63 | """Acquires any lock necessary to access this Storage. 64 | 65 | This lock is not reentrant.""" 66 | self._lock.acquire() 67 | 68 | def release_lock(self): 69 | """Release the Storage lock. 70 | 71 | Trying to release a lock that isn't held will result in a 72 | RuntimeError. 73 | """ 74 | self._lock.release() 75 | 76 | def locked_get(self): 77 | """Retrieve Credential from file. 78 | 79 | Returns: 80 | oauth2client.client.Credentials 81 | """ 82 | credentials = None 83 | content = keyring.get_password(self._service_name, self._user_name) 84 | 85 | if content is not None: 86 | try: 87 | credentials = Credentials.new_from_json(content) 88 | credentials.set_store(self) 89 | except ValueError: 90 | pass 91 | 92 | return credentials 93 | 94 | def locked_put(self, credentials): 95 | """Write Credentials to file. 96 | 97 | Args: 98 | credentials: Credentials, the credentials to store. 99 | """ 100 | keyring.set_password(self._service_name, self._user_name, 101 | credentials.to_json()) 102 | 103 | def locked_delete(self): 104 | """Delete Credentials file. 105 | 106 | Args: 107 | credentials: Credentials, the credentials to store. 108 | """ 109 | keyring.set_password(self._service_name, self._user_name, '') 110 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/lib/oauth2client/keyring_storage.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2012 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """A keyring based Storage. 16 | 17 | A Storage for Credentials that uses the keyring module. 18 | """ 19 | 20 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 21 | 22 | import keyring 23 | import threading 24 | 25 | from client import Storage as BaseStorage 26 | from client import Credentials 27 | 28 | 29 | class Storage(BaseStorage): 30 | """Store and retrieve a single credential to and from the keyring. 31 | 32 | To use this module you must have the keyring module installed. See 33 | . This is an optional module and is not 34 | installed with oauth2client by default because it does not work on all the 35 | platforms that oauth2client supports, such as Google App Engine. 36 | 37 | The keyring module is a cross-platform 38 | library for access the keyring capabilities of the local system. The user will 39 | be prompted for their keyring password when this module is used, and the 40 | manner in which the user is prompted will vary per platform. 41 | 42 | Usage: 43 | from oauth2client.keyring_storage import Storage 44 | 45 | s = Storage('name_of_application', 'user1') 46 | credentials = s.get() 47 | 48 | """ 49 | 50 | def __init__(self, service_name, user_name): 51 | """Constructor. 52 | 53 | Args: 54 | service_name: string, The name of the service under which the credentials 55 | are stored. 56 | user_name: string, The name of the user to store credentials for. 57 | """ 58 | self._service_name = service_name 59 | self._user_name = user_name 60 | self._lock = threading.Lock() 61 | 62 | def acquire_lock(self): 63 | """Acquires any lock necessary to access this Storage. 64 | 65 | This lock is not reentrant.""" 66 | self._lock.acquire() 67 | 68 | def release_lock(self): 69 | """Release the Storage lock. 70 | 71 | Trying to release a lock that isn't held will result in a 72 | RuntimeError. 73 | """ 74 | self._lock.release() 75 | 76 | def locked_get(self): 77 | """Retrieve Credential from file. 78 | 79 | Returns: 80 | oauth2client.client.Credentials 81 | """ 82 | credentials = None 83 | content = keyring.get_password(self._service_name, self._user_name) 84 | 85 | if content is not None: 86 | try: 87 | credentials = Credentials.new_from_json(content) 88 | credentials.set_store(self) 89 | except ValueError: 90 | pass 91 | 92 | return credentials 93 | 94 | def locked_put(self, credentials): 95 | """Write Credentials to file. 96 | 97 | Args: 98 | credentials: Credentials, the credentials to store. 99 | """ 100 | keyring.set_password(self._service_name, self._user_name, 101 | credentials.to_json()) 102 | 103 | def locked_delete(self): 104 | """Delete Credentials file. 105 | 106 | Args: 107 | credentials: Credentials, the credentials to store. 108 | """ 109 | keyring.set_password(self._service_name, self._user_name, '') 110 | -------------------------------------------------------------------------------- /examples/hangout_companion/README.md: -------------------------------------------------------------------------------- 1 | # hangout_companion 2 | 3 | ### Description 4 | 5 | An example of a Hangout application that accesses the Mirror API. And for added fun it is written in Dart. 6 | 7 | Hangouts API documentation: https://developers.google.com/+/hangouts/ 8 | 9 | Mirror API documentation: https://developers.google.com/glass/ 10 | 11 | 12 | ### Setup 13 | 14 | If you have access to the real Mirror API use the dependency as specified in `pubspec.yaml.real` 15 | ``` 16 | google_mirror_v1_api: any 17 | ``` 18 | 19 | 20 | If you don't have access to the real Mirror API and want to test this with your own hosted 21 | [Mirror API Emulator](https://github.com/Scarygami/mirror-api) you will have to do the following: 22 | 23 | 1. Setup and deploy the [Mirror API Emulator](https://github.com/Scarygami/mirror-api) as described there. 24 | 25 | 2. Get the [Dart Client Library Generator](https://github.com/dart-gde/discovery_api_dart_client_generator). 26 | 27 | 3. Run the generator with this command: 28 | 29 | ``` 30 | dart generate.dart --url=https://.appspot.com/_ah/api/discovery/v1/apis/mirror/v1/rest 31 | ``` 32 | 33 | 4. Change the `google_mirror_v1_api` dependency in `pubspec.yaml` to point to the generated library 34 | (see `pubspec.yaml.test`) 35 | 36 | ``` 37 | google_mirror_v1_api: 38 | path: /path/to/dart_mirror_v1_api_client 39 | ``` 40 | 41 | ### Running 42 | 43 | First make sure to change the css and dart paths in `hangout_companion.xml` to match where you plan to upload the files. 44 | 45 | To test your application you will first have to upload it (including the packages) to `` 46 | 47 | Then go to the [Google APIs Console](https://code.google.com/apis/console/) and create a new Project. 48 | 49 | Create a new `Client ID` for web applications in "API Access" 50 | 51 | Activate the Google+ Hangouts API, Google+ API (and Mirror API if you have access) in "Services" 52 | 53 | In "Hangouts" enter the URL to your XML file in Application URL. 54 | 55 | Check "This application requires additional OAuth 2.0 scopes" and enter: 56 | ``` 57 | https://www.googleapis.com/auth/glass.timeline 58 | https://www.googleapis.com/auth/userinfo.email 59 | ``` 60 | 61 | If you have access to the real Mirror API you can remove the `userinfo.email` scope. 62 | Make sure to remove it from the `SCOPES` in `hangout_companion.dart` as well. 63 | 64 | At the bottom of that page you can then "Save" and "Enter a hangout". 65 | 66 | (Of course this will only work in Dartium without compiling to js...) 67 | 68 | 69 | ### Special note if you are running with the Mirror API emulator and not the real Mirror API 70 | 71 | During the first run the authentication will fail, check the Javascript console and you will see a message `Client ID: ` 72 | 73 | Copy this client ID and add it as `additional_client_id` in the `client_secrets.json` of your Mirror API instance. Deploy the instance again and authentication should work. 74 | 75 | Please note that you will have to repeat those steps whenever you save the Hangouts settings in the API console, because this will result in a new Client ID. 76 | 77 | 78 | ### Licenses 79 | 80 | ``` 81 | Copyright (c) 2013 Gerwin Sturm, FoldedSoft e.U. / www.foldedsoft.at 82 | 83 | Licensed under the Apache License, Version 2.0 (the "License"); you may not 84 | use this file except in compliance with the License. You may obtain a copy of 85 | the License at 86 | 87 | http://www.apache.org/licenses/LICENSE-2.0 88 | 89 | Unless required by applicable law or agreed to in writing, software 90 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 91 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 92 | License for the specific language governing permissions and limitations under 93 | the License 94 | 95 | ``` 96 | -------------------------------------------------------------------------------- /mirror_api_server/lib/oauth2client/file.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Utilities for OAuth. 16 | 17 | Utilities for making it easier to work with OAuth 2.0 18 | credentials. 19 | """ 20 | 21 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 22 | 23 | import os 24 | import stat 25 | import threading 26 | 27 | from anyjson import simplejson 28 | from client import Storage as BaseStorage 29 | from client import Credentials 30 | 31 | 32 | class CredentialsFileSymbolicLinkError(Exception): 33 | """Credentials files must not be symbolic links.""" 34 | 35 | 36 | class Storage(BaseStorage): 37 | """Store and retrieve a single credential to and from a file.""" 38 | 39 | def __init__(self, filename): 40 | self._filename = filename 41 | self._lock = threading.Lock() 42 | 43 | def _validate_file(self): 44 | if os.path.islink(self._filename): 45 | raise CredentialsFileSymbolicLinkError( 46 | 'File: %s is a symbolic link.' % self._filename) 47 | 48 | def acquire_lock(self): 49 | """Acquires any lock necessary to access this Storage. 50 | 51 | This lock is not reentrant.""" 52 | self._lock.acquire() 53 | 54 | def release_lock(self): 55 | """Release the Storage lock. 56 | 57 | Trying to release a lock that isn't held will result in a 58 | RuntimeError. 59 | """ 60 | self._lock.release() 61 | 62 | def locked_get(self): 63 | """Retrieve Credential from file. 64 | 65 | Returns: 66 | oauth2client.client.Credentials 67 | 68 | Raises: 69 | CredentialsFileSymbolicLinkError if the file is a symbolic link. 70 | """ 71 | credentials = None 72 | self._validate_file() 73 | try: 74 | f = open(self._filename, 'rb') 75 | content = f.read() 76 | f.close() 77 | except IOError: 78 | return credentials 79 | 80 | try: 81 | credentials = Credentials.new_from_json(content) 82 | credentials.set_store(self) 83 | except ValueError: 84 | pass 85 | 86 | return credentials 87 | 88 | def _create_file_if_needed(self): 89 | """Create an empty file if necessary. 90 | 91 | This method will not initialize the file. Instead it implements a 92 | simple version of "touch" to ensure the file has been created. 93 | """ 94 | if not os.path.exists(self._filename): 95 | old_umask = os.umask(0177) 96 | try: 97 | open(self._filename, 'a+b').close() 98 | finally: 99 | os.umask(old_umask) 100 | 101 | def locked_put(self, credentials): 102 | """Write Credentials to file. 103 | 104 | Args: 105 | credentials: Credentials, the credentials to store. 106 | 107 | Raises: 108 | CredentialsFileSymbolicLinkError if the file is a symbolic link. 109 | """ 110 | 111 | self._create_file_if_needed() 112 | self._validate_file() 113 | f = open(self._filename, 'wb') 114 | f.write(credentials.to_json()) 115 | f.close() 116 | 117 | def locked_delete(self): 118 | """Delete Credentials file. 119 | 120 | Args: 121 | credentials: Credentials, the credentials to store. 122 | """ 123 | 124 | os.unlink(self._filename) 125 | -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/lib/oauth2client/keyring_storage.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2012 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """A keyring based Storage. 16 | 17 | A Storage for Credentials that uses the keyring module. 18 | """ 19 | 20 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 21 | 22 | import keyring 23 | import threading 24 | 25 | from client import Storage as BaseStorage 26 | from client import Credentials 27 | 28 | 29 | class Storage(BaseStorage): 30 | """Store and retrieve a single credential to and from the keyring. 31 | 32 | To use this module you must have the keyring module installed. See 33 | . This is an optional module and is not 34 | installed with oauth2client by default because it does not work on all the 35 | platforms that oauth2client supports, such as Google App Engine. 36 | 37 | The keyring module is a cross-platform 38 | library for access the keyring capabilities of the local system. The user will 39 | be prompted for their keyring password when this module is used, and the 40 | manner in which the user is prompted will vary per platform. 41 | 42 | Usage: 43 | from oauth2client.keyring_storage import Storage 44 | 45 | s = Storage('name_of_application', 'user1') 46 | credentials = s.get() 47 | 48 | """ 49 | 50 | def __init__(self, service_name, user_name): 51 | """Constructor. 52 | 53 | Args: 54 | service_name: string, The name of the service under which the credentials 55 | are stored. 56 | user_name: string, The name of the user to store credentials for. 57 | """ 58 | self._service_name = service_name 59 | self._user_name = user_name 60 | self._lock = threading.Lock() 61 | 62 | def acquire_lock(self): 63 | """Acquires any lock necessary to access this Storage. 64 | 65 | This lock is not reentrant.""" 66 | self._lock.acquire() 67 | 68 | def release_lock(self): 69 | """Release the Storage lock. 70 | 71 | Trying to release a lock that isn't held will result in a 72 | RuntimeError. 73 | """ 74 | self._lock.release() 75 | 76 | def locked_get(self): 77 | """Retrieve Credential from file. 78 | 79 | Returns: 80 | oauth2client.client.Credentials 81 | """ 82 | credentials = None 83 | content = keyring.get_password(self._service_name, self._user_name) 84 | 85 | if content is not None: 86 | try: 87 | credentials = Credentials.new_from_json(content) 88 | credentials.set_store(self) 89 | except ValueError: 90 | pass 91 | 92 | return credentials 93 | 94 | def locked_put(self, credentials): 95 | """Write Credentials to file. 96 | 97 | Args: 98 | credentials: Credentials, the credentials to store. 99 | """ 100 | keyring.set_password(self._service_name, self._user_name, 101 | credentials.to_json()) 102 | 103 | def locked_delete(self): 104 | """Delete Credentials file. 105 | 106 | Args: 107 | credentials: Credentials, the credentials to store. 108 | """ 109 | keyring.set_password(self._service_name, self._user_name, '') 110 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/lib/oauth2client/file.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Utilities for OAuth. 16 | 17 | Utilities for making it easier to work with OAuth 2.0 18 | credentials. 19 | """ 20 | 21 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 22 | 23 | import os 24 | import stat 25 | import threading 26 | 27 | from anyjson import simplejson 28 | from client import Storage as BaseStorage 29 | from client import Credentials 30 | 31 | 32 | class CredentialsFileSymbolicLinkError(Exception): 33 | """Credentials files must not be symbolic links.""" 34 | 35 | 36 | class Storage(BaseStorage): 37 | """Store and retrieve a single credential to and from a file.""" 38 | 39 | def __init__(self, filename): 40 | self._filename = filename 41 | self._lock = threading.Lock() 42 | 43 | def _validate_file(self): 44 | if os.path.islink(self._filename): 45 | raise CredentialsFileSymbolicLinkError( 46 | 'File: %s is a symbolic link.' % self._filename) 47 | 48 | def acquire_lock(self): 49 | """Acquires any lock necessary to access this Storage. 50 | 51 | This lock is not reentrant.""" 52 | self._lock.acquire() 53 | 54 | def release_lock(self): 55 | """Release the Storage lock. 56 | 57 | Trying to release a lock that isn't held will result in a 58 | RuntimeError. 59 | """ 60 | self._lock.release() 61 | 62 | def locked_get(self): 63 | """Retrieve Credential from file. 64 | 65 | Returns: 66 | oauth2client.client.Credentials 67 | 68 | Raises: 69 | CredentialsFileSymbolicLinkError if the file is a symbolic link. 70 | """ 71 | credentials = None 72 | self._validate_file() 73 | try: 74 | f = open(self._filename, 'rb') 75 | content = f.read() 76 | f.close() 77 | except IOError: 78 | return credentials 79 | 80 | try: 81 | credentials = Credentials.new_from_json(content) 82 | credentials.set_store(self) 83 | except ValueError: 84 | pass 85 | 86 | return credentials 87 | 88 | def _create_file_if_needed(self): 89 | """Create an empty file if necessary. 90 | 91 | This method will not initialize the file. Instead it implements a 92 | simple version of "touch" to ensure the file has been created. 93 | """ 94 | if not os.path.exists(self._filename): 95 | old_umask = os.umask(0177) 96 | try: 97 | open(self._filename, 'a+b').close() 98 | finally: 99 | os.umask(old_umask) 100 | 101 | def locked_put(self, credentials): 102 | """Write Credentials to file. 103 | 104 | Args: 105 | credentials: Credentials, the credentials to store. 106 | 107 | Raises: 108 | CredentialsFileSymbolicLinkError if the file is a symbolic link. 109 | """ 110 | 111 | self._create_file_if_needed() 112 | self._validate_file() 113 | f = open(self._filename, 'wb') 114 | f.write(credentials.to_json()) 115 | f.close() 116 | 117 | def locked_delete(self): 118 | """Delete Credentials file. 119 | 120 | Args: 121 | credentials: Credentials, the credentials to store. 122 | """ 123 | 124 | os.unlink(self._filename) 125 | -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/lib/oauth2client/file.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Utilities for OAuth. 16 | 17 | Utilities for making it easier to work with OAuth 2.0 18 | credentials. 19 | """ 20 | 21 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 22 | 23 | import os 24 | import stat 25 | import threading 26 | 27 | from anyjson import simplejson 28 | from client import Storage as BaseStorage 29 | from client import Credentials 30 | 31 | 32 | class CredentialsFileSymbolicLinkError(Exception): 33 | """Credentials files must not be symbolic links.""" 34 | 35 | 36 | class Storage(BaseStorage): 37 | """Store and retrieve a single credential to and from a file.""" 38 | 39 | def __init__(self, filename): 40 | self._filename = filename 41 | self._lock = threading.Lock() 42 | 43 | def _validate_file(self): 44 | if os.path.islink(self._filename): 45 | raise CredentialsFileSymbolicLinkError( 46 | 'File: %s is a symbolic link.' % self._filename) 47 | 48 | def acquire_lock(self): 49 | """Acquires any lock necessary to access this Storage. 50 | 51 | This lock is not reentrant.""" 52 | self._lock.acquire() 53 | 54 | def release_lock(self): 55 | """Release the Storage lock. 56 | 57 | Trying to release a lock that isn't held will result in a 58 | RuntimeError. 59 | """ 60 | self._lock.release() 61 | 62 | def locked_get(self): 63 | """Retrieve Credential from file. 64 | 65 | Returns: 66 | oauth2client.client.Credentials 67 | 68 | Raises: 69 | CredentialsFileSymbolicLinkError if the file is a symbolic link. 70 | """ 71 | credentials = None 72 | self._validate_file() 73 | try: 74 | f = open(self._filename, 'rb') 75 | content = f.read() 76 | f.close() 77 | except IOError: 78 | return credentials 79 | 80 | try: 81 | credentials = Credentials.new_from_json(content) 82 | credentials.set_store(self) 83 | except ValueError: 84 | pass 85 | 86 | return credentials 87 | 88 | def _create_file_if_needed(self): 89 | """Create an empty file if necessary. 90 | 91 | This method will not initialize the file. Instead it implements a 92 | simple version of "touch" to ensure the file has been created. 93 | """ 94 | if not os.path.exists(self._filename): 95 | old_umask = os.umask(0177) 96 | try: 97 | open(self._filename, 'a+b').close() 98 | finally: 99 | os.umask(old_umask) 100 | 101 | def locked_put(self, credentials): 102 | """Write Credentials to file. 103 | 104 | Args: 105 | credentials: Credentials, the credentials to store. 106 | 107 | Raises: 108 | CredentialsFileSymbolicLinkError if the file is a symbolic link. 109 | """ 110 | 111 | self._create_file_if_needed() 112 | self._validate_file() 113 | f = open(self._filename, 'wb') 114 | f.write(credentials.to_json()) 115 | f.close() 116 | 117 | def locked_delete(self): 118 | """Delete Credentials file. 119 | 120 | Args: 121 | credentials: Credentials, the credentials to store. 122 | """ 123 | 124 | os.unlink(self._filename) 125 | -------------------------------------------------------------------------------- /mirror_api_server/lib/oauth2client/xsrfutil.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.5 2 | # 3 | # Copyright 2010 the Melange authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | """Helper methods for creating & verifying XSRF tokens.""" 18 | 19 | __authors__ = [ 20 | '"Doug Coker" ', 21 | '"Joe Gregorio" ', 22 | ] 23 | 24 | 25 | import base64 26 | import hmac 27 | import os # for urandom 28 | import time 29 | 30 | from oauth2client import util 31 | 32 | 33 | # Delimiter character 34 | DELIMITER = ':' 35 | 36 | # 1 hour in seconds 37 | DEFAULT_TIMEOUT_SECS = 1*60*60 38 | 39 | @util.positional(2) 40 | def generate_token(key, user_id, action_id="", when=None): 41 | """Generates a URL-safe token for the given user, action, time tuple. 42 | 43 | Args: 44 | key: secret key to use. 45 | user_id: the user ID of the authenticated user. 46 | action_id: a string identifier of the action they requested 47 | authorization for. 48 | when: the time in seconds since the epoch at which the user was 49 | authorized for this action. If not set the current time is used. 50 | 51 | Returns: 52 | A string XSRF protection token. 53 | """ 54 | when = when or int(time.time()) 55 | digester = hmac.new(key) 56 | digester.update(str(user_id)) 57 | digester.update(DELIMITER) 58 | digester.update(action_id) 59 | digester.update(DELIMITER) 60 | digester.update(str(when)) 61 | digest = digester.digest() 62 | 63 | token = base64.urlsafe_b64encode('%s%s%d' % (digest, 64 | DELIMITER, 65 | when)) 66 | return token 67 | 68 | 69 | @util.positional(3) 70 | def validate_token(key, token, user_id, action_id="", current_time=None): 71 | """Validates that the given token authorizes the user for the action. 72 | 73 | Tokens are invalid if the time of issue is too old or if the token 74 | does not match what generateToken outputs (i.e. the token was forged). 75 | 76 | Args: 77 | key: secret key to use. 78 | token: a string of the token generated by generateToken. 79 | user_id: the user ID of the authenticated user. 80 | action_id: a string identifier of the action they requested 81 | authorization for. 82 | 83 | Returns: 84 | A boolean - True if the user is authorized for the action, False 85 | otherwise. 86 | """ 87 | if not token: 88 | return False 89 | try: 90 | decoded = base64.urlsafe_b64decode(str(token)) 91 | token_time = long(decoded.split(DELIMITER)[-1]) 92 | except (TypeError, ValueError): 93 | return False 94 | if current_time is None: 95 | current_time = time.time() 96 | # If the token is too old it's not valid. 97 | if current_time - token_time > DEFAULT_TIMEOUT_SECS: 98 | return False 99 | 100 | # The given token should match the generated one with the same time. 101 | expected_token = generate_token(key, user_id, action_id=action_id, 102 | when=token_time) 103 | if len(token) != len(expected_token): 104 | return False 105 | 106 | # Perform constant time comparison to avoid timing attacks 107 | different = 0 108 | for x, y in zip(token, expected_token): 109 | different |= ord(x) ^ ord(y) 110 | if different: 111 | return False 112 | 113 | return True 114 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/lib/oauth2client/xsrfutil.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.5 2 | # 3 | # Copyright 2010 the Melange authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | """Helper methods for creating & verifying XSRF tokens.""" 18 | 19 | __authors__ = [ 20 | '"Doug Coker" ', 21 | '"Joe Gregorio" ', 22 | ] 23 | 24 | 25 | import base64 26 | import hmac 27 | import os # for urandom 28 | import time 29 | 30 | from oauth2client import util 31 | 32 | 33 | # Delimiter character 34 | DELIMITER = ':' 35 | 36 | # 1 hour in seconds 37 | DEFAULT_TIMEOUT_SECS = 1*60*60 38 | 39 | @util.positional(2) 40 | def generate_token(key, user_id, action_id="", when=None): 41 | """Generates a URL-safe token for the given user, action, time tuple. 42 | 43 | Args: 44 | key: secret key to use. 45 | user_id: the user ID of the authenticated user. 46 | action_id: a string identifier of the action they requested 47 | authorization for. 48 | when: the time in seconds since the epoch at which the user was 49 | authorized for this action. If not set the current time is used. 50 | 51 | Returns: 52 | A string XSRF protection token. 53 | """ 54 | when = when or int(time.time()) 55 | digester = hmac.new(key) 56 | digester.update(str(user_id)) 57 | digester.update(DELIMITER) 58 | digester.update(action_id) 59 | digester.update(DELIMITER) 60 | digester.update(str(when)) 61 | digest = digester.digest() 62 | 63 | token = base64.urlsafe_b64encode('%s%s%d' % (digest, 64 | DELIMITER, 65 | when)) 66 | return token 67 | 68 | 69 | @util.positional(3) 70 | def validate_token(key, token, user_id, action_id="", current_time=None): 71 | """Validates that the given token authorizes the user for the action. 72 | 73 | Tokens are invalid if the time of issue is too old or if the token 74 | does not match what generateToken outputs (i.e. the token was forged). 75 | 76 | Args: 77 | key: secret key to use. 78 | token: a string of the token generated by generateToken. 79 | user_id: the user ID of the authenticated user. 80 | action_id: a string identifier of the action they requested 81 | authorization for. 82 | 83 | Returns: 84 | A boolean - True if the user is authorized for the action, False 85 | otherwise. 86 | """ 87 | if not token: 88 | return False 89 | try: 90 | decoded = base64.urlsafe_b64decode(str(token)) 91 | token_time = long(decoded.split(DELIMITER)[-1]) 92 | except (TypeError, ValueError): 93 | return False 94 | if current_time is None: 95 | current_time = time.time() 96 | # If the token is too old it's not valid. 97 | if current_time - token_time > DEFAULT_TIMEOUT_SECS: 98 | return False 99 | 100 | # The given token should match the generated one with the same time. 101 | expected_token = generate_token(key, user_id, action_id=action_id, 102 | when=token_time) 103 | if len(token) != len(expected_token): 104 | return False 105 | 106 | # Perform constant time comparison to avoid timing attacks 107 | different = 0 108 | for x, y in zip(token, expected_token): 109 | different |= ord(x) ^ ord(y) 110 | if different: 111 | return False 112 | 113 | return True 114 | -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/lib/oauth2client/xsrfutil.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.5 2 | # 3 | # Copyright 2010 the Melange authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | """Helper methods for creating & verifying XSRF tokens.""" 18 | 19 | __authors__ = [ 20 | '"Doug Coker" ', 21 | '"Joe Gregorio" ', 22 | ] 23 | 24 | 25 | import base64 26 | import hmac 27 | import os # for urandom 28 | import time 29 | 30 | from oauth2client import util 31 | 32 | 33 | # Delimiter character 34 | DELIMITER = ':' 35 | 36 | # 1 hour in seconds 37 | DEFAULT_TIMEOUT_SECS = 1*60*60 38 | 39 | @util.positional(2) 40 | def generate_token(key, user_id, action_id="", when=None): 41 | """Generates a URL-safe token for the given user, action, time tuple. 42 | 43 | Args: 44 | key: secret key to use. 45 | user_id: the user ID of the authenticated user. 46 | action_id: a string identifier of the action they requested 47 | authorization for. 48 | when: the time in seconds since the epoch at which the user was 49 | authorized for this action. If not set the current time is used. 50 | 51 | Returns: 52 | A string XSRF protection token. 53 | """ 54 | when = when or int(time.time()) 55 | digester = hmac.new(key) 56 | digester.update(str(user_id)) 57 | digester.update(DELIMITER) 58 | digester.update(action_id) 59 | digester.update(DELIMITER) 60 | digester.update(str(when)) 61 | digest = digester.digest() 62 | 63 | token = base64.urlsafe_b64encode('%s%s%d' % (digest, 64 | DELIMITER, 65 | when)) 66 | return token 67 | 68 | 69 | @util.positional(3) 70 | def validate_token(key, token, user_id, action_id="", current_time=None): 71 | """Validates that the given token authorizes the user for the action. 72 | 73 | Tokens are invalid if the time of issue is too old or if the token 74 | does not match what generateToken outputs (i.e. the token was forged). 75 | 76 | Args: 77 | key: secret key to use. 78 | token: a string of the token generated by generateToken. 79 | user_id: the user ID of the authenticated user. 80 | action_id: a string identifier of the action they requested 81 | authorization for. 82 | 83 | Returns: 84 | A boolean - True if the user is authorized for the action, False 85 | otherwise. 86 | """ 87 | if not token: 88 | return False 89 | try: 90 | decoded = base64.urlsafe_b64decode(str(token)) 91 | token_time = long(decoded.split(DELIMITER)[-1]) 92 | except (TypeError, ValueError): 93 | return False 94 | if current_time is None: 95 | current_time = time.time() 96 | # If the token is too old it's not valid. 97 | if current_time - token_time > DEFAULT_TIMEOUT_SECS: 98 | return False 99 | 100 | # The given token should match the generated one with the same time. 101 | expected_token = generate_token(key, user_id, action_id=action_id, 102 | when=token_time) 103 | if len(token) != len(expected_token): 104 | return False 105 | 106 | # Perform constant time comparison to avoid timing attacks 107 | different = 0 108 | for x, y in zip(token, expected_token): 109 | different |= ord(x) ^ ord(y) 110 | if different: 111 | return False 112 | 113 | return True 114 | -------------------------------------------------------------------------------- /examples/hangout_companion/web/src/HangoutOAuth2.dart: -------------------------------------------------------------------------------- 1 | library hangoutoauth2; 2 | 3 | import "dart:async"; 4 | import "dart:html"; 5 | import "dart:json" as JSON; 6 | import "package:google_oauth2_client/google_oauth2_browser.dart"; 7 | import "package:js/js.dart" as js; 8 | 9 | /* 10 | * OAuth2 authentication using the Google JS API client inside of a Hangout. 11 | * 12 | * This doesn't require additional User interaction since the User already gave 13 | * permissions when they started the Hangout application 14 | */ 15 | class HangoutOAuth2 extends SimpleOAuth2 { 16 | 17 | bool _clientLoaded = false; 18 | Future _clientLoader; 19 | 20 | DateTime _expiry; 21 | 22 | Set _scopes = new Set(); 23 | 24 | /* 25 | * [scopes] has to match the additional OAuth Scopes you defined for the 26 | * Hangout application in the API Console 27 | */ 28 | HangoutOAuth2(List scopes) : super(null) { 29 | _scopes.addAll(scopes); 30 | _scopes.add("https://www.googleapis.com/auth/plus.me"); 31 | _scopes.add("https://www.googleapis.com/auth/hangout.av"); 32 | _scopes.add("https://www.googleapis.com/auth/hangout.participants"); 33 | } 34 | 35 | /* 36 | * Loads the Google JS API client if it hasn't been loaded before 37 | */ 38 | Future _loadClient() { 39 | if (_clientLoaded) return new Future.value(true); 40 | if (_clientLoader != null) return _clientLoader; 41 | 42 | var completer = new Completer(); 43 | js.scoped(() { 44 | js.context.onClientReady = new js.Callback.once(() { 45 | _clientLoaded = true; 46 | _clientLoader = null; 47 | completer.complete(true); 48 | }); 49 | }); 50 | 51 | ScriptElement script = new ScriptElement(); 52 | script.src = "https://apis.google.com/js/client.js?onload=onClientReady"; 53 | script.type = "text/javascript"; 54 | document.body.children.add(script); 55 | 56 | _clientLoader = completer.future; 57 | return _clientLoader; 58 | } 59 | 60 | /* 61 | * Takes a normal HttpRequest and sets the authentication headers 62 | * if a valid token is available or can be retrieved 63 | */ 64 | Future authenticate(HttpRequest request) { 65 | var completer = new Completer(); 66 | login().then((t) { 67 | if (token != null) { 68 | super.authenticate(request).then( 69 | (authenticatedRequest) => completer.complete(authenticatedRequest) 70 | ); 71 | } else { 72 | completer.complete(request); 73 | } 74 | }); 75 | return completer.future; 76 | } 77 | 78 | /* 79 | * Uses the JS API Client to retrieve a valid OAuth2 token 80 | * or returns the current token if it is still valid 81 | */ 82 | Future login() { 83 | if (token != null && _expiry != null && _expiry.isAfter(new DateTime.now())) { 84 | return new Future.value(token); 85 | } 86 | var completer = new Completer(); 87 | _loadClient().then((success) { 88 | if (success) { 89 | js.scoped(() { 90 | js.context.gapi.auth.authorize( 91 | js.map({"client_id": null, "scope": new List.from(_scopes), "immediate": true}), 92 | new js.Callback.once((js.Proxy authResult) { 93 | Map result = JSON.parse(js.context.JSON.stringify(authResult)); 94 | print("Client ID: ${result["client_id"]}"); 95 | if (result.containsKey("access_token")) { 96 | token = result["access_token"]; 97 | tokenType = result["token_type"]; 98 | _expiry = new DateTime.now().add(new Duration(seconds: int.parse(result["expires_in"]))); 99 | completer.complete(result["access_token"]); 100 | } else { 101 | token = null; 102 | _expiry = null; 103 | completer.complete(null); 104 | } 105 | }) 106 | ); 107 | }); 108 | } 109 | }); 110 | return completer.future; 111 | } 112 | } -------------------------------------------------------------------------------- /mirror_api_server/lib/apiclient/errors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.4 2 | # 3 | # Copyright (C) 2010 Google Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | """Errors for the library. 18 | 19 | All exceptions defined by the library 20 | should be defined in this file. 21 | """ 22 | 23 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 24 | 25 | 26 | from oauth2client import util 27 | from oauth2client.anyjson import simplejson 28 | 29 | 30 | class Error(Exception): 31 | """Base error for this module.""" 32 | pass 33 | 34 | 35 | class HttpError(Error): 36 | """HTTP data was invalid or unexpected.""" 37 | 38 | @util.positional(3) 39 | def __init__(self, resp, content, uri=None): 40 | self.resp = resp 41 | self.content = content 42 | self.uri = uri 43 | 44 | def _get_reason(self): 45 | """Calculate the reason for the error from the response content.""" 46 | reason = self.resp.reason 47 | try: 48 | data = simplejson.loads(self.content) 49 | reason = data['error']['message'] 50 | except (ValueError, KeyError): 51 | pass 52 | if reason is None: 53 | reason = '' 54 | return reason 55 | 56 | def __repr__(self): 57 | if self.uri: 58 | return '' % ( 59 | self.resp.status, self.uri, self._get_reason().strip()) 60 | else: 61 | return '' % (self.resp.status, self._get_reason()) 62 | 63 | __str__ = __repr__ 64 | 65 | 66 | class InvalidJsonError(Error): 67 | """The JSON returned could not be parsed.""" 68 | pass 69 | 70 | 71 | class UnknownFileType(Error): 72 | """File type unknown or unexpected.""" 73 | pass 74 | 75 | 76 | class UnknownLinkType(Error): 77 | """Link type unknown or unexpected.""" 78 | pass 79 | 80 | 81 | class UnknownApiNameOrVersion(Error): 82 | """No API with that name and version exists.""" 83 | pass 84 | 85 | 86 | class UnacceptableMimeTypeError(Error): 87 | """That is an unacceptable mimetype for this operation.""" 88 | pass 89 | 90 | 91 | class MediaUploadSizeError(Error): 92 | """Media is larger than the method can accept.""" 93 | pass 94 | 95 | 96 | class ResumableUploadError(HttpError): 97 | """Error occured during resumable upload.""" 98 | pass 99 | 100 | 101 | class InvalidChunkSizeError(Error): 102 | """The given chunksize is not valid.""" 103 | pass 104 | 105 | 106 | class BatchError(HttpError): 107 | """Error occured during batch operations.""" 108 | 109 | @util.positional(2) 110 | def __init__(self, reason, resp=None, content=None): 111 | self.resp = resp 112 | self.content = content 113 | self.reason = reason 114 | 115 | def __repr__(self): 116 | return '' % (self.resp.status, self.reason) 117 | 118 | __str__ = __repr__ 119 | 120 | 121 | class UnexpectedMethodError(Error): 122 | """Exception raised by RequestMockBuilder on unexpected calls.""" 123 | 124 | @util.positional(1) 125 | def __init__(self, methodId=None): 126 | """Constructor for an UnexpectedMethodError.""" 127 | super(UnexpectedMethodError, self).__init__( 128 | 'Received unexpected call %s' % methodId) 129 | 130 | 131 | class UnexpectedBodyError(Error): 132 | """Exception raised by RequestMockBuilder on unexpected bodies.""" 133 | 134 | def __init__(self, expected, provided): 135 | """Constructor for an UnexpectedMethodError.""" 136 | super(UnexpectedBodyError, self).__init__( 137 | 'Expected: [%s] - Provided: [%s]' % (expected, provided)) 138 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/lib/apiclient/errors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.4 2 | # 3 | # Copyright (C) 2010 Google Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | """Errors for the library. 18 | 19 | All exceptions defined by the library 20 | should be defined in this file. 21 | """ 22 | 23 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 24 | 25 | 26 | from oauth2client import util 27 | from oauth2client.anyjson import simplejson 28 | 29 | 30 | class Error(Exception): 31 | """Base error for this module.""" 32 | pass 33 | 34 | 35 | class HttpError(Error): 36 | """HTTP data was invalid or unexpected.""" 37 | 38 | @util.positional(3) 39 | def __init__(self, resp, content, uri=None): 40 | self.resp = resp 41 | self.content = content 42 | self.uri = uri 43 | 44 | def _get_reason(self): 45 | """Calculate the reason for the error from the response content.""" 46 | reason = self.resp.reason 47 | try: 48 | data = simplejson.loads(self.content) 49 | reason = data['error']['message'] 50 | except (ValueError, KeyError): 51 | pass 52 | if reason is None: 53 | reason = '' 54 | return reason 55 | 56 | def __repr__(self): 57 | if self.uri: 58 | return '' % ( 59 | self.resp.status, self.uri, self._get_reason().strip()) 60 | else: 61 | return '' % (self.resp.status, self._get_reason()) 62 | 63 | __str__ = __repr__ 64 | 65 | 66 | class InvalidJsonError(Error): 67 | """The JSON returned could not be parsed.""" 68 | pass 69 | 70 | 71 | class UnknownFileType(Error): 72 | """File type unknown or unexpected.""" 73 | pass 74 | 75 | 76 | class UnknownLinkType(Error): 77 | """Link type unknown or unexpected.""" 78 | pass 79 | 80 | 81 | class UnknownApiNameOrVersion(Error): 82 | """No API with that name and version exists.""" 83 | pass 84 | 85 | 86 | class UnacceptableMimeTypeError(Error): 87 | """That is an unacceptable mimetype for this operation.""" 88 | pass 89 | 90 | 91 | class MediaUploadSizeError(Error): 92 | """Media is larger than the method can accept.""" 93 | pass 94 | 95 | 96 | class ResumableUploadError(HttpError): 97 | """Error occured during resumable upload.""" 98 | pass 99 | 100 | 101 | class InvalidChunkSizeError(Error): 102 | """The given chunksize is not valid.""" 103 | pass 104 | 105 | 106 | class BatchError(HttpError): 107 | """Error occured during batch operations.""" 108 | 109 | @util.positional(2) 110 | def __init__(self, reason, resp=None, content=None): 111 | self.resp = resp 112 | self.content = content 113 | self.reason = reason 114 | 115 | def __repr__(self): 116 | return '' % (self.resp.status, self.reason) 117 | 118 | __str__ = __repr__ 119 | 120 | 121 | class UnexpectedMethodError(Error): 122 | """Exception raised by RequestMockBuilder on unexpected calls.""" 123 | 124 | @util.positional(1) 125 | def __init__(self, methodId=None): 126 | """Constructor for an UnexpectedMethodError.""" 127 | super(UnexpectedMethodError, self).__init__( 128 | 'Received unexpected call %s' % methodId) 129 | 130 | 131 | class UnexpectedBodyError(Error): 132 | """Exception raised by RequestMockBuilder on unexpected bodies.""" 133 | 134 | def __init__(self, expected, provided): 135 | """Constructor for an UnexpectedMethodError.""" 136 | super(UnexpectedBodyError, self).__init__( 137 | 'Expected: [%s] - Provided: [%s]' % (expected, provided)) 138 | -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/lib/apiclient/errors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.4 2 | # 3 | # Copyright (C) 2010 Google Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | """Errors for the library. 18 | 19 | All exceptions defined by the library 20 | should be defined in this file. 21 | """ 22 | 23 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 24 | 25 | 26 | from oauth2client import util 27 | from oauth2client.anyjson import simplejson 28 | 29 | 30 | class Error(Exception): 31 | """Base error for this module.""" 32 | pass 33 | 34 | 35 | class HttpError(Error): 36 | """HTTP data was invalid or unexpected.""" 37 | 38 | @util.positional(3) 39 | def __init__(self, resp, content, uri=None): 40 | self.resp = resp 41 | self.content = content 42 | self.uri = uri 43 | 44 | def _get_reason(self): 45 | """Calculate the reason for the error from the response content.""" 46 | reason = self.resp.reason 47 | try: 48 | data = simplejson.loads(self.content) 49 | reason = data['error']['message'] 50 | except (ValueError, KeyError): 51 | pass 52 | if reason is None: 53 | reason = '' 54 | return reason 55 | 56 | def __repr__(self): 57 | if self.uri: 58 | return '' % ( 59 | self.resp.status, self.uri, self._get_reason().strip()) 60 | else: 61 | return '' % (self.resp.status, self._get_reason()) 62 | 63 | __str__ = __repr__ 64 | 65 | 66 | class InvalidJsonError(Error): 67 | """The JSON returned could not be parsed.""" 68 | pass 69 | 70 | 71 | class UnknownFileType(Error): 72 | """File type unknown or unexpected.""" 73 | pass 74 | 75 | 76 | class UnknownLinkType(Error): 77 | """Link type unknown or unexpected.""" 78 | pass 79 | 80 | 81 | class UnknownApiNameOrVersion(Error): 82 | """No API with that name and version exists.""" 83 | pass 84 | 85 | 86 | class UnacceptableMimeTypeError(Error): 87 | """That is an unacceptable mimetype for this operation.""" 88 | pass 89 | 90 | 91 | class MediaUploadSizeError(Error): 92 | """Media is larger than the method can accept.""" 93 | pass 94 | 95 | 96 | class ResumableUploadError(HttpError): 97 | """Error occured during resumable upload.""" 98 | pass 99 | 100 | 101 | class InvalidChunkSizeError(Error): 102 | """The given chunksize is not valid.""" 103 | pass 104 | 105 | 106 | class BatchError(HttpError): 107 | """Error occured during batch operations.""" 108 | 109 | @util.positional(2) 110 | def __init__(self, reason, resp=None, content=None): 111 | self.resp = resp 112 | self.content = content 113 | self.reason = reason 114 | 115 | def __repr__(self): 116 | return '' % (self.resp.status, self.reason) 117 | 118 | __str__ = __repr__ 119 | 120 | 121 | class UnexpectedMethodError(Error): 122 | """Exception raised by RequestMockBuilder on unexpected calls.""" 123 | 124 | @util.positional(1) 125 | def __init__(self, methodId=None): 126 | """Constructor for an UnexpectedMethodError.""" 127 | super(UnexpectedMethodError, self).__init__( 128 | 'Received unexpected call %s' % methodId) 129 | 130 | 131 | class UnexpectedBodyError(Error): 132 | """Exception raised by RequestMockBuilder on unexpected bodies.""" 133 | 134 | def __init__(self, expected, provided): 135 | """Constructor for an UnexpectedMethodError.""" 136 | super(UnexpectedBodyError, self).__init__( 137 | 'Expected: [%s] - Provided: [%s]' % (expected, provided)) 138 | -------------------------------------------------------------------------------- /mirror_api_server/demos/add_a_cat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2013 Gerwin Sturm, FoldedSoft e.U. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0# 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Methods for Add a Cat to that service""" 17 | 18 | __author__ = 'scarygami@gmail.com (Gerwin Sturm)' 19 | 20 | from service import upload 21 | from utils import base_url 22 | 23 | import logging 24 | import Image 25 | import cStringIO 26 | import random 27 | 28 | __all__ = ["handle_item", "CONTACTS", "WELCOMES"] 29 | 30 | """Contacts that need to registered when the user connects to this service""" 31 | CONTACTS = [ 32 | { 33 | "acceptTypes": "image/*", 34 | "id": "add_a_cat", 35 | "displayName": "Add a Cat to that", 36 | "imageUrls": [base_url + "/images/cat.png"] 37 | } 38 | ] 39 | 40 | """Welcome message cards that are sent when the user first connects to this service""" 41 | WELCOMES = [ 42 | { 43 | "html": ("
" 44 | " " 45 | "
" 46 | "
" 47 | "

Welcome to Add a Cat!

" 48 | "
" 49 | "
") 50 | } 51 | ] 52 | 53 | _NUM_CATS = 6 54 | 55 | 56 | def handle_item(item, notification, service, test): 57 | """Callback for Timeline updates.""" 58 | 59 | if "userActions" in notification: 60 | for action in notification["userActions"]: 61 | if "type" in action and action["type"] == "SHARE": 62 | break 63 | else: 64 | # No SHARE action 65 | return 66 | else: 67 | # No SHARE action 68 | return 69 | 70 | if "recipients" in item: 71 | for rec in item["recipients"]: 72 | if rec["id"] == "add_a_cat": 73 | break 74 | else: 75 | # Item not meant for this service 76 | return 77 | else: 78 | # Item not meant for this service 79 | return 80 | 81 | imageId = None 82 | if "attachments" in item: 83 | for att in item["attachments"]: 84 | if att["contentType"].startswith("image/"): 85 | imageId = att["id"] 86 | break 87 | 88 | if imageId is None: 89 | logging.info("No suitable attachment") 90 | return 91 | 92 | attachment_metadata = service.timeline().attachments().get( 93 | itemId=item["id"], attachmentId=imageId).execute() 94 | content_url = attachment_metadata.get("contentUrl") 95 | resp, content = service._http.request(content_url) 96 | 97 | if resp.status != 200: 98 | logging.info("Couldn't fetch attachment") 99 | 100 | tempimg = cStringIO.StringIO(content) 101 | im = Image.open(tempimg) 102 | 103 | cat = random.randint(1, _NUM_CATS) 104 | cat_image = Image.open("res/cat%s.png" % cat) 105 | 106 | zoom = im.size[0] / 640 107 | 108 | cat_image.resize((cat_image.size[0] * zoom, cat_image.size[1] * zoom), Image.ANTIALIAS) 109 | 110 | x = random.randint(0, im.size[0] - cat_image.size[0]) 111 | y = random.randint(0, im.size[1] - cat_image.size[1]) 112 | 113 | im.paste(cat_image, (x, y), cat_image) 114 | 115 | f = cStringIO.StringIO() 116 | im.save(f, "JPEG") 117 | content = f.getvalue() 118 | f.close() 119 | 120 | new_item = {} 121 | new_item["menuItems"] = [{"action": "SHARE"}] 122 | 123 | result = upload.multipart_insert(new_item, content, "image/jpeg", service, test) 124 | logging.info(result) 125 | -------------------------------------------------------------------------------- /mirror_api_server/lib/httplib2/iri2uri.py: -------------------------------------------------------------------------------- 1 | """ 2 | iri2uri 3 | 4 | Converts an IRI to a URI. 5 | 6 | """ 7 | __author__ = "Joe Gregorio (joe@bitworking.org)" 8 | __copyright__ = "Copyright 2006, Joe Gregorio" 9 | __contributors__ = [] 10 | __version__ = "1.0.0" 11 | __license__ = "MIT" 12 | __history__ = """ 13 | """ 14 | 15 | import urlparse 16 | 17 | 18 | # Convert an IRI to a URI following the rules in RFC 3987 19 | # 20 | # The characters we need to enocde and escape are defined in the spec: 21 | # 22 | # iprivate = %xE000-F8FF / %xF0000-FFFFD / %x100000-10FFFD 23 | # ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF 24 | # / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD 25 | # / %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD 26 | # / %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD 27 | # / %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD 28 | # / %xD0000-DFFFD / %xE1000-EFFFD 29 | 30 | escape_range = [ 31 | (0xA0, 0xD7FF), 32 | (0xE000, 0xF8FF), 33 | (0xF900, 0xFDCF), 34 | (0xFDF0, 0xFFEF), 35 | (0x10000, 0x1FFFD), 36 | (0x20000, 0x2FFFD), 37 | (0x30000, 0x3FFFD), 38 | (0x40000, 0x4FFFD), 39 | (0x50000, 0x5FFFD), 40 | (0x60000, 0x6FFFD), 41 | (0x70000, 0x7FFFD), 42 | (0x80000, 0x8FFFD), 43 | (0x90000, 0x9FFFD), 44 | (0xA0000, 0xAFFFD), 45 | (0xB0000, 0xBFFFD), 46 | (0xC0000, 0xCFFFD), 47 | (0xD0000, 0xDFFFD), 48 | (0xE1000, 0xEFFFD), 49 | (0xF0000, 0xFFFFD), 50 | (0x100000, 0x10FFFD), 51 | ] 52 | 53 | def encode(c): 54 | retval = c 55 | i = ord(c) 56 | for low, high in escape_range: 57 | if i < low: 58 | break 59 | if i >= low and i <= high: 60 | retval = "".join(["%%%2X" % ord(o) for o in c.encode('utf-8')]) 61 | break 62 | return retval 63 | 64 | 65 | def iri2uri(uri): 66 | """Convert an IRI to a URI. Note that IRIs must be 67 | passed in a unicode strings. That is, do not utf-8 encode 68 | the IRI before passing it into the function.""" 69 | if isinstance(uri ,unicode): 70 | (scheme, authority, path, query, fragment) = urlparse.urlsplit(uri) 71 | authority = authority.encode('idna') 72 | # For each character in 'ucschar' or 'iprivate' 73 | # 1. encode as utf-8 74 | # 2. then %-encode each octet of that utf-8 75 | uri = urlparse.urlunsplit((scheme, authority, path, query, fragment)) 76 | uri = "".join([encode(c) for c in uri]) 77 | return uri 78 | 79 | if __name__ == "__main__": 80 | import unittest 81 | 82 | class Test(unittest.TestCase): 83 | 84 | def test_uris(self): 85 | """Test that URIs are invariant under the transformation.""" 86 | invariant = [ 87 | u"ftp://ftp.is.co.za/rfc/rfc1808.txt", 88 | u"http://www.ietf.org/rfc/rfc2396.txt", 89 | u"ldap://[2001:db8::7]/c=GB?objectClass?one", 90 | u"mailto:John.Doe@example.com", 91 | u"news:comp.infosystems.www.servers.unix", 92 | u"tel:+1-816-555-1212", 93 | u"telnet://192.0.2.16:80/", 94 | u"urn:oasis:names:specification:docbook:dtd:xml:4.1.2" ] 95 | for uri in invariant: 96 | self.assertEqual(uri, iri2uri(uri)) 97 | 98 | def test_iri(self): 99 | """ Test that the right type of escaping is done for each part of the URI.""" 100 | self.assertEqual("http://xn--o3h.com/%E2%98%84", iri2uri(u"http://\N{COMET}.com/\N{COMET}")) 101 | self.assertEqual("http://bitworking.org/?fred=%E2%98%84", iri2uri(u"http://bitworking.org/?fred=\N{COMET}")) 102 | self.assertEqual("http://bitworking.org/#%E2%98%84", iri2uri(u"http://bitworking.org/#\N{COMET}")) 103 | self.assertEqual("#%E2%98%84", iri2uri(u"#\N{COMET}")) 104 | self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}")) 105 | self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}"))) 106 | self.assertNotEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}".encode('utf-8'))) 107 | 108 | unittest.main() 109 | 110 | 111 | -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/service.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2013 Gerwin Sturm, FoldedSoft e.U. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0# 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """RequestHandlers for HCT Web service""" 17 | 18 | __author__ = 'scarygami@gmail.com (Gerwin Sturm)' 19 | 20 | import utils 21 | from auth import get_auth_service 22 | 23 | import json 24 | import logging 25 | import random 26 | import string 27 | 28 | from google.appengine.ext import ndb 29 | from oauth2client.client import AccessTokenRefreshError 30 | 31 | 32 | class IndexHandler(utils.BaseHandler): 33 | """Renders the main page that is mainly used for authentication only so far""" 34 | 35 | def get(self, test): 36 | 37 | if test is None: 38 | scopes = ' '.join(utils.COMMON_SCOPES + utils.REAL_SCOPES) 39 | else: 40 | scopes = ' '.join(utils.COMMON_SCOPES + utils.TEST_SCOPES) 41 | 42 | reconnect = (self.request.get("reconnect") == "true") 43 | template = utils.JINJA.get_template("templates/service.html") 44 | state = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in xrange(32)) 45 | self.session["state"] = state 46 | self.response.out.write(template.render({"client_id": utils.CLIENT_ID, "state": state, "scopes": scopes, "reconnect": reconnect})) 47 | 48 | 49 | class ListHandler(utils.BaseHandler): 50 | 51 | def get(self, test): 52 | """Retrieve currently tracked sources for the current user.""" 53 | 54 | self.response.content_type = "application/json" 55 | 56 | gplus_id = self.session.get("gplus_id") 57 | if test is not None: 58 | user = ndb.Key("TestUser", gplus_id).get() 59 | else: 60 | user = ndb.Key("User", gplus_id).get() 61 | 62 | if user is None: 63 | self.response.status = 401 64 | self.response.out.write(utils.createError(401, "Current user not connected.")) 65 | return 66 | 67 | items = [] 68 | if user.sources is not None: 69 | for source in user.sources: 70 | data = source.get() 71 | items.append(data.id) 72 | 73 | self.response.out.write(json.dumps({"items": items})) 74 | 75 | # TODO 76 | 77 | 78 | class AddHandler(utils.BaseHandler): 79 | 80 | def post(self, test): 81 | """Add a new source to be tracker.""" 82 | 83 | self.response.content_type = "application/json" 84 | 85 | gplus_id = self.session.get("gplus_id") 86 | service = get_auth_service(gplus_id, test) 87 | 88 | if service is None: 89 | self.response.status = 401 90 | self.response.out.write(utils.createError(401, "Current user not connected.")) 91 | return 92 | 93 | data = json.loads(self.request.body) 94 | 95 | # TODO 96 | logging.info(data) 97 | 98 | 99 | class RemoveHandler(utils.BaseHandler): 100 | 101 | def get(self, test, source_id): 102 | """ 103 | Remove a Source for the user. 104 | Set Source.active = False if no users are tracking it anymore. 105 | """ 106 | 107 | self.response.content_type = "application/json" 108 | 109 | gplus_id = self.session.get("gplus_id") 110 | service = get_auth_service(gplus_id, test) 111 | 112 | if service is None: 113 | self.response.status = 401 114 | self.response.out.write(utils.createError(401, "Current user not connected.")) 115 | return 116 | 117 | # TODO 118 | 119 | 120 | SERVICE_ROUTES = [ 121 | (r"(/test)?/", IndexHandler), 122 | (r"(/test)?/list", ListHandler), 123 | (r"(/test)?/add", AddHandler), 124 | (r"(/test)?/remove/(.+)", RemoveHandler) 125 | ] 126 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/lib/httplib2/iri2uri.py: -------------------------------------------------------------------------------- 1 | """ 2 | iri2uri 3 | 4 | Converts an IRI to a URI. 5 | 6 | """ 7 | __author__ = "Joe Gregorio (joe@bitworking.org)" 8 | __copyright__ = "Copyright 2006, Joe Gregorio" 9 | __contributors__ = [] 10 | __version__ = "1.0.0" 11 | __license__ = "MIT" 12 | __history__ = """ 13 | """ 14 | 15 | import urlparse 16 | 17 | 18 | # Convert an IRI to a URI following the rules in RFC 3987 19 | # 20 | # The characters we need to enocde and escape are defined in the spec: 21 | # 22 | # iprivate = %xE000-F8FF / %xF0000-FFFFD / %x100000-10FFFD 23 | # ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF 24 | # / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD 25 | # / %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD 26 | # / %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD 27 | # / %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD 28 | # / %xD0000-DFFFD / %xE1000-EFFFD 29 | 30 | escape_range = [ 31 | (0xA0, 0xD7FF), 32 | (0xE000, 0xF8FF), 33 | (0xF900, 0xFDCF), 34 | (0xFDF0, 0xFFEF), 35 | (0x10000, 0x1FFFD), 36 | (0x20000, 0x2FFFD), 37 | (0x30000, 0x3FFFD), 38 | (0x40000, 0x4FFFD), 39 | (0x50000, 0x5FFFD), 40 | (0x60000, 0x6FFFD), 41 | (0x70000, 0x7FFFD), 42 | (0x80000, 0x8FFFD), 43 | (0x90000, 0x9FFFD), 44 | (0xA0000, 0xAFFFD), 45 | (0xB0000, 0xBFFFD), 46 | (0xC0000, 0xCFFFD), 47 | (0xD0000, 0xDFFFD), 48 | (0xE1000, 0xEFFFD), 49 | (0xF0000, 0xFFFFD), 50 | (0x100000, 0x10FFFD), 51 | ] 52 | 53 | def encode(c): 54 | retval = c 55 | i = ord(c) 56 | for low, high in escape_range: 57 | if i < low: 58 | break 59 | if i >= low and i <= high: 60 | retval = "".join(["%%%2X" % ord(o) for o in c.encode('utf-8')]) 61 | break 62 | return retval 63 | 64 | 65 | def iri2uri(uri): 66 | """Convert an IRI to a URI. Note that IRIs must be 67 | passed in a unicode strings. That is, do not utf-8 encode 68 | the IRI before passing it into the function.""" 69 | if isinstance(uri ,unicode): 70 | (scheme, authority, path, query, fragment) = urlparse.urlsplit(uri) 71 | authority = authority.encode('idna') 72 | # For each character in 'ucschar' or 'iprivate' 73 | # 1. encode as utf-8 74 | # 2. then %-encode each octet of that utf-8 75 | uri = urlparse.urlunsplit((scheme, authority, path, query, fragment)) 76 | uri = "".join([encode(c) for c in uri]) 77 | return uri 78 | 79 | if __name__ == "__main__": 80 | import unittest 81 | 82 | class Test(unittest.TestCase): 83 | 84 | def test_uris(self): 85 | """Test that URIs are invariant under the transformation.""" 86 | invariant = [ 87 | u"ftp://ftp.is.co.za/rfc/rfc1808.txt", 88 | u"http://www.ietf.org/rfc/rfc2396.txt", 89 | u"ldap://[2001:db8::7]/c=GB?objectClass?one", 90 | u"mailto:John.Doe@example.com", 91 | u"news:comp.infosystems.www.servers.unix", 92 | u"tel:+1-816-555-1212", 93 | u"telnet://192.0.2.16:80/", 94 | u"urn:oasis:names:specification:docbook:dtd:xml:4.1.2" ] 95 | for uri in invariant: 96 | self.assertEqual(uri, iri2uri(uri)) 97 | 98 | def test_iri(self): 99 | """ Test that the right type of escaping is done for each part of the URI.""" 100 | self.assertEqual("http://xn--o3h.com/%E2%98%84", iri2uri(u"http://\N{COMET}.com/\N{COMET}")) 101 | self.assertEqual("http://bitworking.org/?fred=%E2%98%84", iri2uri(u"http://bitworking.org/?fred=\N{COMET}")) 102 | self.assertEqual("http://bitworking.org/#%E2%98%84", iri2uri(u"http://bitworking.org/#\N{COMET}")) 103 | self.assertEqual("#%E2%98%84", iri2uri(u"#\N{COMET}")) 104 | self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}")) 105 | self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}"))) 106 | self.assertNotEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}".encode('utf-8'))) 107 | 108 | unittest.main() 109 | 110 | 111 | -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/lib/httplib2/iri2uri.py: -------------------------------------------------------------------------------- 1 | """ 2 | iri2uri 3 | 4 | Converts an IRI to a URI. 5 | 6 | """ 7 | __author__ = "Joe Gregorio (joe@bitworking.org)" 8 | __copyright__ = "Copyright 2006, Joe Gregorio" 9 | __contributors__ = [] 10 | __version__ = "1.0.0" 11 | __license__ = "MIT" 12 | __history__ = """ 13 | """ 14 | 15 | import urlparse 16 | 17 | 18 | # Convert an IRI to a URI following the rules in RFC 3987 19 | # 20 | # The characters we need to enocde and escape are defined in the spec: 21 | # 22 | # iprivate = %xE000-F8FF / %xF0000-FFFFD / %x100000-10FFFD 23 | # ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF 24 | # / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD 25 | # / %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD 26 | # / %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD 27 | # / %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD 28 | # / %xD0000-DFFFD / %xE1000-EFFFD 29 | 30 | escape_range = [ 31 | (0xA0, 0xD7FF), 32 | (0xE000, 0xF8FF), 33 | (0xF900, 0xFDCF), 34 | (0xFDF0, 0xFFEF), 35 | (0x10000, 0x1FFFD), 36 | (0x20000, 0x2FFFD), 37 | (0x30000, 0x3FFFD), 38 | (0x40000, 0x4FFFD), 39 | (0x50000, 0x5FFFD), 40 | (0x60000, 0x6FFFD), 41 | (0x70000, 0x7FFFD), 42 | (0x80000, 0x8FFFD), 43 | (0x90000, 0x9FFFD), 44 | (0xA0000, 0xAFFFD), 45 | (0xB0000, 0xBFFFD), 46 | (0xC0000, 0xCFFFD), 47 | (0xD0000, 0xDFFFD), 48 | (0xE1000, 0xEFFFD), 49 | (0xF0000, 0xFFFFD), 50 | (0x100000, 0x10FFFD), 51 | ] 52 | 53 | def encode(c): 54 | retval = c 55 | i = ord(c) 56 | for low, high in escape_range: 57 | if i < low: 58 | break 59 | if i >= low and i <= high: 60 | retval = "".join(["%%%2X" % ord(o) for o in c.encode('utf-8')]) 61 | break 62 | return retval 63 | 64 | 65 | def iri2uri(uri): 66 | """Convert an IRI to a URI. Note that IRIs must be 67 | passed in a unicode strings. That is, do not utf-8 encode 68 | the IRI before passing it into the function.""" 69 | if isinstance(uri ,unicode): 70 | (scheme, authority, path, query, fragment) = urlparse.urlsplit(uri) 71 | authority = authority.encode('idna') 72 | # For each character in 'ucschar' or 'iprivate' 73 | # 1. encode as utf-8 74 | # 2. then %-encode each octet of that utf-8 75 | uri = urlparse.urlunsplit((scheme, authority, path, query, fragment)) 76 | uri = "".join([encode(c) for c in uri]) 77 | return uri 78 | 79 | if __name__ == "__main__": 80 | import unittest 81 | 82 | class Test(unittest.TestCase): 83 | 84 | def test_uris(self): 85 | """Test that URIs are invariant under the transformation.""" 86 | invariant = [ 87 | u"ftp://ftp.is.co.za/rfc/rfc1808.txt", 88 | u"http://www.ietf.org/rfc/rfc2396.txt", 89 | u"ldap://[2001:db8::7]/c=GB?objectClass?one", 90 | u"mailto:John.Doe@example.com", 91 | u"news:comp.infosystems.www.servers.unix", 92 | u"tel:+1-816-555-1212", 93 | u"telnet://192.0.2.16:80/", 94 | u"urn:oasis:names:specification:docbook:dtd:xml:4.1.2" ] 95 | for uri in invariant: 96 | self.assertEqual(uri, iri2uri(uri)) 97 | 98 | def test_iri(self): 99 | """ Test that the right type of escaping is done for each part of the URI.""" 100 | self.assertEqual("http://xn--o3h.com/%E2%98%84", iri2uri(u"http://\N{COMET}.com/\N{COMET}")) 101 | self.assertEqual("http://bitworking.org/?fred=%E2%98%84", iri2uri(u"http://bitworking.org/?fred=\N{COMET}")) 102 | self.assertEqual("http://bitworking.org/#%E2%98%84", iri2uri(u"http://bitworking.org/#\N{COMET}")) 103 | self.assertEqual("#%E2%98%84", iri2uri(u"#\N{COMET}")) 104 | self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}")) 105 | self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}"))) 106 | self.assertNotEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}".encode('utf-8'))) 107 | 108 | unittest.main() 109 | 110 | 111 | -------------------------------------------------------------------------------- /mirror_api_server/lib/oauth2client/django_orm.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """OAuth 2.0 utilities for Django. 16 | 17 | Utilities for using OAuth 2.0 in conjunction with 18 | the Django datastore. 19 | """ 20 | 21 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 22 | 23 | import oauth2client 24 | import base64 25 | import pickle 26 | 27 | from django.db import models 28 | from oauth2client.client import Storage as BaseStorage 29 | 30 | class CredentialsField(models.Field): 31 | 32 | __metaclass__ = models.SubfieldBase 33 | 34 | def __init__(self, *args, **kwargs): 35 | if 'null' not in kwargs: 36 | kwargs['null'] = True 37 | super(CredentialsField, self).__init__(*args, **kwargs) 38 | 39 | def get_internal_type(self): 40 | return "TextField" 41 | 42 | def to_python(self, value): 43 | if value is None: 44 | return None 45 | if isinstance(value, oauth2client.client.Credentials): 46 | return value 47 | return pickle.loads(base64.b64decode(value)) 48 | 49 | def get_db_prep_value(self, value, connection, prepared=False): 50 | if value is None: 51 | return None 52 | return base64.b64encode(pickle.dumps(value)) 53 | 54 | 55 | class FlowField(models.Field): 56 | 57 | __metaclass__ = models.SubfieldBase 58 | 59 | def __init__(self, *args, **kwargs): 60 | if 'null' not in kwargs: 61 | kwargs['null'] = True 62 | super(FlowField, self).__init__(*args, **kwargs) 63 | 64 | def get_internal_type(self): 65 | return "TextField" 66 | 67 | def to_python(self, value): 68 | if value is None: 69 | return None 70 | if isinstance(value, oauth2client.client.Flow): 71 | return value 72 | return pickle.loads(base64.b64decode(value)) 73 | 74 | def get_db_prep_value(self, value, connection, prepared=False): 75 | if value is None: 76 | return None 77 | return base64.b64encode(pickle.dumps(value)) 78 | 79 | 80 | class Storage(BaseStorage): 81 | """Store and retrieve a single credential to and from 82 | the datastore. 83 | 84 | This Storage helper presumes the Credentials 85 | have been stored as a CredenialsField 86 | on a db model class. 87 | """ 88 | 89 | def __init__(self, model_class, key_name, key_value, property_name): 90 | """Constructor for Storage. 91 | 92 | Args: 93 | model: db.Model, model class 94 | key_name: string, key name for the entity that has the credentials 95 | key_value: string, key value for the entity that has the credentials 96 | property_name: string, name of the property that is an CredentialsProperty 97 | """ 98 | self.model_class = model_class 99 | self.key_name = key_name 100 | self.key_value = key_value 101 | self.property_name = property_name 102 | 103 | def locked_get(self): 104 | """Retrieve Credential from datastore. 105 | 106 | Returns: 107 | oauth2client.Credentials 108 | """ 109 | credential = None 110 | 111 | query = {self.key_name: self.key_value} 112 | entities = self.model_class.objects.filter(**query) 113 | if len(entities) > 0: 114 | credential = getattr(entities[0], self.property_name) 115 | if credential and hasattr(credential, 'set_store'): 116 | credential.set_store(self) 117 | return credential 118 | 119 | def locked_put(self, credentials): 120 | """Write a Credentials to the datastore. 121 | 122 | Args: 123 | credentials: Credentials, the credentials to store. 124 | """ 125 | args = {self.key_name: self.key_value} 126 | entity = self.model_class(**args) 127 | setattr(entity, self.property_name, credentials) 128 | entity.save() 129 | 130 | def locked_delete(self): 131 | """Delete Credentials from the datastore.""" 132 | 133 | query = {self.key_name: self.key_value} 134 | entities = self.model_class.objects.filter(**query).delete() 135 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/lib/oauth2client/django_orm.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """OAuth 2.0 utilities for Django. 16 | 17 | Utilities for using OAuth 2.0 in conjunction with 18 | the Django datastore. 19 | """ 20 | 21 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 22 | 23 | import oauth2client 24 | import base64 25 | import pickle 26 | 27 | from django.db import models 28 | from oauth2client.client import Storage as BaseStorage 29 | 30 | class CredentialsField(models.Field): 31 | 32 | __metaclass__ = models.SubfieldBase 33 | 34 | def __init__(self, *args, **kwargs): 35 | if 'null' not in kwargs: 36 | kwargs['null'] = True 37 | super(CredentialsField, self).__init__(*args, **kwargs) 38 | 39 | def get_internal_type(self): 40 | return "TextField" 41 | 42 | def to_python(self, value): 43 | if value is None: 44 | return None 45 | if isinstance(value, oauth2client.client.Credentials): 46 | return value 47 | return pickle.loads(base64.b64decode(value)) 48 | 49 | def get_db_prep_value(self, value, connection, prepared=False): 50 | if value is None: 51 | return None 52 | return base64.b64encode(pickle.dumps(value)) 53 | 54 | 55 | class FlowField(models.Field): 56 | 57 | __metaclass__ = models.SubfieldBase 58 | 59 | def __init__(self, *args, **kwargs): 60 | if 'null' not in kwargs: 61 | kwargs['null'] = True 62 | super(FlowField, self).__init__(*args, **kwargs) 63 | 64 | def get_internal_type(self): 65 | return "TextField" 66 | 67 | def to_python(self, value): 68 | if value is None: 69 | return None 70 | if isinstance(value, oauth2client.client.Flow): 71 | return value 72 | return pickle.loads(base64.b64decode(value)) 73 | 74 | def get_db_prep_value(self, value, connection, prepared=False): 75 | if value is None: 76 | return None 77 | return base64.b64encode(pickle.dumps(value)) 78 | 79 | 80 | class Storage(BaseStorage): 81 | """Store and retrieve a single credential to and from 82 | the datastore. 83 | 84 | This Storage helper presumes the Credentials 85 | have been stored as a CredenialsField 86 | on a db model class. 87 | """ 88 | 89 | def __init__(self, model_class, key_name, key_value, property_name): 90 | """Constructor for Storage. 91 | 92 | Args: 93 | model: db.Model, model class 94 | key_name: string, key name for the entity that has the credentials 95 | key_value: string, key value for the entity that has the credentials 96 | property_name: string, name of the property that is an CredentialsProperty 97 | """ 98 | self.model_class = model_class 99 | self.key_name = key_name 100 | self.key_value = key_value 101 | self.property_name = property_name 102 | 103 | def locked_get(self): 104 | """Retrieve Credential from datastore. 105 | 106 | Returns: 107 | oauth2client.Credentials 108 | """ 109 | credential = None 110 | 111 | query = {self.key_name: self.key_value} 112 | entities = self.model_class.objects.filter(**query) 113 | if len(entities) > 0: 114 | credential = getattr(entities[0], self.property_name) 115 | if credential and hasattr(credential, 'set_store'): 116 | credential.set_store(self) 117 | return credential 118 | 119 | def locked_put(self, credentials): 120 | """Write a Credentials to the datastore. 121 | 122 | Args: 123 | credentials: Credentials, the credentials to store. 124 | """ 125 | args = {self.key_name: self.key_value} 126 | entity = self.model_class(**args) 127 | setattr(entity, self.property_name, credentials) 128 | entity.save() 129 | 130 | def locked_delete(self): 131 | """Delete Credentials from the datastore.""" 132 | 133 | query = {self.key_name: self.key_value} 134 | entities = self.model_class.objects.filter(**query).delete() 135 | -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/lib/oauth2client/django_orm.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """OAuth 2.0 utilities for Django. 16 | 17 | Utilities for using OAuth 2.0 in conjunction with 18 | the Django datastore. 19 | """ 20 | 21 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 22 | 23 | import oauth2client 24 | import base64 25 | import pickle 26 | 27 | from django.db import models 28 | from oauth2client.client import Storage as BaseStorage 29 | 30 | class CredentialsField(models.Field): 31 | 32 | __metaclass__ = models.SubfieldBase 33 | 34 | def __init__(self, *args, **kwargs): 35 | if 'null' not in kwargs: 36 | kwargs['null'] = True 37 | super(CredentialsField, self).__init__(*args, **kwargs) 38 | 39 | def get_internal_type(self): 40 | return "TextField" 41 | 42 | def to_python(self, value): 43 | if value is None: 44 | return None 45 | if isinstance(value, oauth2client.client.Credentials): 46 | return value 47 | return pickle.loads(base64.b64decode(value)) 48 | 49 | def get_db_prep_value(self, value, connection, prepared=False): 50 | if value is None: 51 | return None 52 | return base64.b64encode(pickle.dumps(value)) 53 | 54 | 55 | class FlowField(models.Field): 56 | 57 | __metaclass__ = models.SubfieldBase 58 | 59 | def __init__(self, *args, **kwargs): 60 | if 'null' not in kwargs: 61 | kwargs['null'] = True 62 | super(FlowField, self).__init__(*args, **kwargs) 63 | 64 | def get_internal_type(self): 65 | return "TextField" 66 | 67 | def to_python(self, value): 68 | if value is None: 69 | return None 70 | if isinstance(value, oauth2client.client.Flow): 71 | return value 72 | return pickle.loads(base64.b64decode(value)) 73 | 74 | def get_db_prep_value(self, value, connection, prepared=False): 75 | if value is None: 76 | return None 77 | return base64.b64encode(pickle.dumps(value)) 78 | 79 | 80 | class Storage(BaseStorage): 81 | """Store and retrieve a single credential to and from 82 | the datastore. 83 | 84 | This Storage helper presumes the Credentials 85 | have been stored as a CredenialsField 86 | on a db model class. 87 | """ 88 | 89 | def __init__(self, model_class, key_name, key_value, property_name): 90 | """Constructor for Storage. 91 | 92 | Args: 93 | model: db.Model, model class 94 | key_name: string, key name for the entity that has the credentials 95 | key_value: string, key value for the entity that has the credentials 96 | property_name: string, name of the property that is an CredentialsProperty 97 | """ 98 | self.model_class = model_class 99 | self.key_name = key_name 100 | self.key_value = key_value 101 | self.property_name = property_name 102 | 103 | def locked_get(self): 104 | """Retrieve Credential from datastore. 105 | 106 | Returns: 107 | oauth2client.Credentials 108 | """ 109 | credential = None 110 | 111 | query = {self.key_name: self.key_value} 112 | entities = self.model_class.objects.filter(**query) 113 | if len(entities) > 0: 114 | credential = getattr(entities[0], self.property_name) 115 | if credential and hasattr(credential, 'set_store'): 116 | credential.set_store(self) 117 | return credential 118 | 119 | def locked_put(self, credentials): 120 | """Write a Credentials to the datastore. 121 | 122 | Args: 123 | credentials: Credentials, the credentials to store. 124 | """ 125 | args = {self.key_name: self.key_value} 126 | entity = self.model_class(**args) 127 | setattr(entity, self.property_name, credentials) 128 | entity.save() 129 | 130 | def locked_delete(self): 131 | """Delete Credentials from the datastore.""" 132 | 133 | query = {self.key_name: self.key_value} 134 | entities = self.model_class.objects.filter(**query).delete() 135 | -------------------------------------------------------------------------------- /mirror_api_server/service/notify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2013 Gerwin Sturm, FoldedSoft e.U. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0# 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """ 17 | Notification/subscription handler 18 | 19 | Handles subscription post requests coming from the Mirror API and forwards 20 | the requests to the relevant demo services. 21 | 22 | """ 23 | 24 | __author__ = 'scarygami@gmail.com (Gerwin Sturm)' 25 | 26 | import utils 27 | from demos import demo_services 28 | from auth import get_auth_service 29 | 30 | import json 31 | import logging 32 | from datetime import datetime 33 | from google.appengine.ext import ndb 34 | 35 | 36 | class TimelineNotifyHandler(utils.BaseHandler): 37 | """ 38 | Handles all timeline notifications (updates, deletes, inserts) 39 | Forwards the information to implemented demo services 40 | """ 41 | 42 | def post(self, test): 43 | """Callback for Timeline updates.""" 44 | 45 | message = self.request.body 46 | data = json.loads(message) 47 | logging.info(data) 48 | 49 | self.response.status = 200 50 | 51 | gplus_id = data["userToken"] 52 | verifyToken = data["verifyToken"] 53 | if test is not None: 54 | user = ndb.Key("TestUser", gplus_id).get() 55 | else: 56 | user = ndb.Key("User", gplus_id).get() 57 | if user is None or user.verifyToken != verifyToken: 58 | logging.info("Wrong user") 59 | return 60 | 61 | if data["collection"] != "timeline": 62 | logging.info("Wrong collection") 63 | return 64 | 65 | service = get_auth_service(gplus_id, test) 66 | 67 | if service is None: 68 | logging.info("No valid credentials") 69 | return 70 | 71 | result = service.timeline().get(id=data["itemId"]).execute() 72 | logging.info(result) 73 | 74 | for demo_service in demo_services: 75 | if hasattr(demo_service, "handle_item"): 76 | demo_service.handle_item(result, data, service, test) 77 | 78 | 79 | class LocationNotifyHandler(utils.BaseHandler): 80 | """ 81 | Handles all location notifications 82 | Forwards the information to implemented demo services 83 | """ 84 | 85 | def post(self, test): 86 | """Callback for Location updates.""" 87 | 88 | message = self.request.body 89 | data = json.loads(message) 90 | 91 | self.response.status = 200 92 | 93 | gplus_id = data["userToken"] 94 | verifyToken = data["verifyToken"] 95 | if test is not None: 96 | user = ndb.Key("TestUser", gplus_id).get() 97 | else: 98 | user = ndb.Key("User", gplus_id).get() 99 | 100 | if user is None or user.verifyToken != verifyToken: 101 | logging.info("Wrong user") 102 | return 103 | 104 | if data["collection"] != "locations": 105 | logging.info("Wrong collection") 106 | return 107 | 108 | if data["operation"] != "UPDATE": 109 | logging.info("Wrong operation") 110 | return 111 | 112 | service = get_auth_service(gplus_id, test) 113 | 114 | if service is None: 115 | logging.info("No valid credentials") 116 | return 117 | 118 | result = service.locations().get(id=data["itemId"]).execute() 119 | logging.info(result) 120 | 121 | if "longitude" in result and "latitude" in result: 122 | user.longitude = result["longitude"] 123 | user.latitude = result["latitude"] 124 | user.locationUpdate = datetime.utcnow() 125 | user.put() 126 | 127 | for demo_service in demo_services: 128 | if hasattr(demo_service, "handle_location"): 129 | demo_service.handle_location(result, data, service, test) 130 | 131 | 132 | NOTIFY_ROUTES = [ 133 | (r"(/test)?/timeline_update", TimelineNotifyHandler), 134 | (r"(/test)?/locations_update", LocationNotifyHandler) 135 | ] 136 | -------------------------------------------------------------------------------- /mirror_api_server/service/upload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2013 Gerwin Sturm, FoldedSoft e.U. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0# 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Helper functions to upload mediacontent to cards""" 17 | 18 | __author__ = 'scarygami@gmail.com (Gerwin Sturm)' 19 | 20 | from utils import base_url 21 | 22 | import io 23 | import json 24 | import logging 25 | 26 | from apiclient import errors 27 | from apiclient.http import MediaIoBaseUpload 28 | 29 | _BOUNDARY = "-----1234567890abc" 30 | 31 | 32 | def _create_multipart_body(metadata, content, contentType): 33 | base64_data = content.encode("base64").replace("\n", "") 34 | multipart_body = "\r\n--" + _BOUNDARY + "\r\n" 35 | multipart_body += "Content-Type: application/json\r\n\r\n" 36 | multipart_body += json.dumps(metadata) 37 | multipart_body += "\r\n--" + _BOUNDARY + "\r\n" 38 | multipart_body += "Content-Type: " + contentType + "\r\n" 39 | multipart_body += "Content-Transfer-Encoding: base64\r\n\r\n" 40 | multipart_body += base64_data 41 | multipart_body += "\r\n\r\n--" + _BOUNDARY + "--" 42 | 43 | return multipart_body 44 | 45 | 46 | def multipart_insert(metadata, content, contentType, service, test): 47 | 48 | if metadata is None: 49 | metadata = {} 50 | 51 | """Insert a new card with metainfo card and media.""" 52 | if test is None: 53 | # Using the functionality of the API Client library to directly send multipart request 54 | media = MediaIoBaseUpload(io.BytesIO(content), contentType, resumable=True) 55 | try: 56 | return service.timeline().insert(body=metadata, media_body=media).execute() 57 | except errors.HttpError, error: 58 | logging.error("Multipart update error: %s" % error) 59 | return error 60 | 61 | # Constructing the multipart upload for test environement 62 | multipart_body = _create_multipart_body(metadata, content, contentType) 63 | 64 | headers = {} 65 | headers["Content-Type"] = "multipart/related; boundary=\"" + _BOUNDARY + "\"" 66 | 67 | return service._http.request(base_url + "/upload/mirror/v1/timeline", method="POST", body=multipart_body, headers=headers) 68 | 69 | 70 | def multipart_update(cardId, metadata, content, contentType, service, test): 71 | 72 | if metadata is None: 73 | metadata = {} 74 | 75 | """Update a card with metainfo and media.""" 76 | if test is None: 77 | # Using the functionality of the API Client library to directly send multipart request 78 | media = MediaIoBaseUpload(io.BytesIO(content), contentType, resumable=True) 79 | try: 80 | return service.timeline().update(id=cardId, body=metadata, media_body=media).execute() 81 | except errors.HttpError, error: 82 | logging.error("Multipart update error: %s" % error) 83 | return error 84 | 85 | # Constructing the multipart upload for test environement 86 | multipart_body = _create_multipart_body(metadata, content, contentType) 87 | 88 | headers = {} 89 | headers["Content-Type"] = "multipart/related; boundary=\"" + _BOUNDARY + "\"" 90 | 91 | return service._http.request("%s/upload/mirror/v1/timeline/%s" % (base_url, cardId), method="POST", body=multipart_body, headers=headers) 92 | 93 | 94 | def media_insert(cardId, content, contentType, service, test): 95 | 96 | """Insert attachment to an existing card.""" 97 | if test is None: 98 | # Using the functionality of the API Client library to directly send request 99 | media = MediaIoBaseUpload(io.BytesIO(content), contentType, resumable=True) 100 | try: 101 | return service.timeline().attachments().insert(id=cardId, media_body=media).execute() 102 | except errors.HttpError, error: 103 | logging.error("Attachment insert error: %s" % error) 104 | return error 105 | 106 | # Constructing the multipart upload for test environement 107 | multipart_body = _create_multipart_body({}, content, contentType) 108 | 109 | headers = {} 110 | headers["Content-Type"] = "multipart/related; boundary=\"" + _BOUNDARY + "\"" 111 | 112 | return service._http.request("%s/upload/mirror/v1/timeline/%s/attachments" % (base_url, cardId), method="POST", body=multipart_body, headers=headers) 113 | -------------------------------------------------------------------------------- /mirror_api_server/demos/instaglass.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2013 Gerwin Sturm, FoldedSoft e.U. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0# 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Methods for Instaglass service""" 17 | 18 | __author__ = 'scarygami@gmail.com (Gerwin Sturm)' 19 | 20 | from service import upload 21 | from utils import base_url 22 | 23 | import logging 24 | import Image 25 | import ImageOps 26 | import cStringIO 27 | 28 | __all__ = ["handle_item", "CONTACTS", "WELCOMES"] 29 | 30 | """Contacts that need to registered when the user connects to this service""" 31 | CONTACTS = [ 32 | { 33 | "acceptTypes": "image/*", 34 | "id": "instaglass_sepia", 35 | "displayName": "Sepia", 36 | "imageUrls": [base_url + "/images/sepia.jpg"] 37 | } 38 | ] 39 | 40 | """Welcome message cards that are sent when the user first connects to this service""" 41 | WELCOMES = [ 42 | { 43 | "html": ("
" 44 | " " 45 | "
" 46 | "
" 47 | "

Welcome to Instaglass!

" 48 | "
" 49 | "
") 50 | } 51 | ] 52 | 53 | 54 | def _make_linear_ramp(white): 55 | """ generate a palette in a format acceptable for `putpalette`, which 56 | expects [r,g,b,r,g,b,...] 57 | """ 58 | ramp = [] 59 | r, g, b = white 60 | for i in range(255): 61 | ramp.extend((r*i/255, g*i/255, b*i/255)) 62 | return ramp 63 | 64 | 65 | def _apply_sepia_filter(image): 66 | """ Apply a sepia-tone filter to the given PIL Image 67 | Based on code at: http://effbot.org/zone/pil-sepia.htm 68 | """ 69 | # make sepia ramp (tweak color as necessary) 70 | sepia = _make_linear_ramp((255, 240, 192)) 71 | 72 | # convert to grayscale 73 | orig_mode = image.mode 74 | if orig_mode != "L": 75 | image = image.convert("L") 76 | 77 | # apply contrast enhancement here, e.g. 78 | image = ImageOps.autocontrast(image) 79 | 80 | # apply sepia palette 81 | image.putpalette(sepia) 82 | 83 | # convert back to its original mode 84 | if orig_mode != "L": 85 | image = image.convert(orig_mode) 86 | 87 | return image 88 | 89 | 90 | def handle_item(item, notification, service, test): 91 | """Callback for Timeline updates.""" 92 | 93 | if "userActions" in notification: 94 | for action in notification["userActions"]: 95 | if "type" in action and action["type"] == "SHARE": 96 | break 97 | else: 98 | # No SHARE action 99 | return 100 | else: 101 | # No SHARE action 102 | return 103 | 104 | if "recipients" in item: 105 | for rec in item["recipients"]: 106 | if rec["id"] == "instaglass_sepia": 107 | break 108 | else: 109 | # Item not meant for this service 110 | return 111 | else: 112 | # Item not meant for this service 113 | return 114 | 115 | imageId = None 116 | if "attachments" in item: 117 | for att in item["attachments"]: 118 | if att["contentType"].startswith("image/"): 119 | imageId = att["id"] 120 | break 121 | 122 | if imageId is None: 123 | logging.info("No suitable attachment") 124 | return 125 | 126 | attachment_metadata = service.timeline().attachments().get( 127 | itemId=item["id"], attachmentId=imageId).execute() 128 | content_url = attachment_metadata.get("contentUrl") 129 | resp, content = service._http.request(content_url) 130 | 131 | if resp.status != 200: 132 | logging.info("Couldn't fetch attachment") 133 | 134 | tempimg = cStringIO.StringIO(content) 135 | im = Image.open(tempimg) 136 | new_im = _apply_sepia_filter(im) 137 | 138 | f = cStringIO.StringIO() 139 | new_im.save(f, "JPEG") 140 | content = f.getvalue() 141 | f.close() 142 | 143 | new_item = {} 144 | new_item["menuItems"] = [{"action": "SHARE"}] 145 | 146 | result = upload.multipart_insert(new_item, content, "image/jpeg", service, test) 147 | logging.info(result) 148 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/service.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2013 Gerwin Sturm, FoldedSoft e.U. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """RequestHandlers for HCT Web service""" 17 | 18 | __author__ = 'scarygami@gmail.com (Gerwin Sturm)' 19 | 20 | import utils 21 | from auth import get_auth_service 22 | from models import Submission 23 | 24 | import json 25 | import logging 26 | import random 27 | import string 28 | 29 | from google.appengine.ext import ndb 30 | from oauth2client.client import AccessTokenRefreshError 31 | 32 | class IndexHandler(utils.BaseHandler): 33 | """Renders the main page that is mainly used for authentication only so far""" 34 | 35 | def get(self, test): 36 | 37 | if test is None: 38 | scopes = ' '.join(utils.COMMON_SCOPES + utils.REAL_SCOPES) 39 | else: 40 | scopes = ' '.join(utils.COMMON_SCOPES + utils.TEST_SCOPES) 41 | 42 | reconnect = (self.request.get("reconnect") == "true") 43 | template = utils.JINJA.get_template("templates/service.html") 44 | state = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in xrange(32)) 45 | self.session["state"] = state 46 | self.response.out.write(template.render({"client_id": utils.CLIENT_ID, "state": state, "scopes": scopes, "reconnect": reconnect})) 47 | 48 | 49 | class UserHandler(utils.BaseHandler): 50 | 51 | def get(self, test): 52 | """Retrieve submissions for the current user.""" 53 | 54 | self.response.content_type = "application/json" 55 | 56 | gplus_id = self.session.get("gplus_id") 57 | if gplus_id is None: 58 | self.response.status = 401 59 | self.response.out.write(utils.createError(401, "Current user not connected.")) 60 | return 61 | 62 | if test is not None: 63 | user = ndb.Key("TestUser", gplus_id).get() 64 | else: 65 | user = ndb.Key("User", gplus_id).get() 66 | 67 | if user is None: 68 | self.response.status = 401 69 | self.response.out.write(utils.createError(401, "Current user not connected.")) 70 | return 71 | 72 | items = [] 73 | submissions = Submission.query(ancestor=user.key).order(-Submission.date).fetch(50) 74 | for submission in submissions: 75 | items.append({ 76 | "id": submission.key.id(), 77 | "colour": submission.colour, 78 | "url": submission.url, 79 | "date": submission.date.strftime("%Y-%m-%dT%H:%M:%S.%f") 80 | }) 81 | 82 | self.response.out.write(json.dumps({"items": items})) 83 | 84 | 85 | class ListHandler(utils.BaseHandler): 86 | 87 | def get(self, test, colour): 88 | """Retrieve recent submission, limited to a certain colour.""" 89 | 90 | self.response.content_type = "application/json" 91 | 92 | qry = Submission.query() 93 | if colour is not None: 94 | qry = qry.filter(Submission.colour == colour) 95 | qry = qry.order(-Submission.date) 96 | 97 | items = [] 98 | submissions = qry.fetch(50) 99 | for submission in submissions: 100 | items.append({ 101 | "id": submission.key.id(), 102 | "colour": submission.colour, 103 | "url": submission.url, 104 | "date": submission.date.strftime("%Y-%m-%dT%H:%M:%S.%f") 105 | }) 106 | 107 | self.response.out.write(json.dumps({"items": items})) 108 | 109 | 110 | class RemoveHandler(utils.BaseHandler): 111 | 112 | def get(self, test, submission_id): 113 | """Remove a Submission for the user.""" 114 | 115 | self.response.content_type = "application/json" 116 | 117 | gplus_id = self.session.get("gplus_id") 118 | if test is not None: 119 | user = ndb.Key("TestUser", gplus_id).get() 120 | else: 121 | user = ndb.Key("User", gplus_id).get() 122 | 123 | if user.get() is None: 124 | self.response.status = 401 125 | self.response.out.write(utils.createError(401, "Current user not connected.")) 126 | return 127 | 128 | # TODO 129 | 130 | 131 | SERVICE_ROUTES = [ 132 | (r"(/test)?/", IndexHandler), 133 | (r"(/test)?/list/?(red|orange|yellow|green|blue|indigo|violet)?", ListHandler), 134 | (r"(/test)?/user", UserHandler), 135 | (r"(/test)?/remove/(.+)", RemoveHandler) 136 | ] 137 | -------------------------------------------------------------------------------- /mirror_api_server/lib/oauth2client/clientsecrets.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Utilities for reading OAuth 2.0 client secret files. 16 | 17 | A client_secrets.json file contains all the information needed to interact with 18 | an OAuth 2.0 protected service. 19 | """ 20 | 21 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 22 | 23 | 24 | from anyjson import simplejson 25 | 26 | # Properties that make a client_secrets.json file valid. 27 | TYPE_WEB = 'web' 28 | TYPE_INSTALLED = 'installed' 29 | 30 | VALID_CLIENT = { 31 | TYPE_WEB: { 32 | 'required': [ 33 | 'client_id', 34 | 'client_secret', 35 | 'redirect_uris', 36 | 'auth_uri', 37 | 'token_uri', 38 | ], 39 | 'string': [ 40 | 'client_id', 41 | 'client_secret', 42 | ], 43 | }, 44 | TYPE_INSTALLED: { 45 | 'required': [ 46 | 'client_id', 47 | 'client_secret', 48 | 'redirect_uris', 49 | 'auth_uri', 50 | 'token_uri', 51 | ], 52 | 'string': [ 53 | 'client_id', 54 | 'client_secret', 55 | ], 56 | }, 57 | } 58 | 59 | 60 | class Error(Exception): 61 | """Base error for this module.""" 62 | pass 63 | 64 | 65 | class InvalidClientSecretsError(Error): 66 | """Format of ClientSecrets file is invalid.""" 67 | pass 68 | 69 | 70 | def _validate_clientsecrets(obj): 71 | if obj is None or len(obj) != 1: 72 | raise InvalidClientSecretsError('Invalid file format.') 73 | client_type = obj.keys()[0] 74 | if client_type not in VALID_CLIENT.keys(): 75 | raise InvalidClientSecretsError('Unknown client type: %s.' % client_type) 76 | client_info = obj[client_type] 77 | for prop_name in VALID_CLIENT[client_type]['required']: 78 | if prop_name not in client_info: 79 | raise InvalidClientSecretsError( 80 | 'Missing property "%s" in a client type of "%s".' % (prop_name, 81 | client_type)) 82 | for prop_name in VALID_CLIENT[client_type]['string']: 83 | if client_info[prop_name].startswith('[['): 84 | raise InvalidClientSecretsError( 85 | 'Property "%s" is not configured.' % prop_name) 86 | return client_type, client_info 87 | 88 | 89 | def load(fp): 90 | obj = simplejson.load(fp) 91 | return _validate_clientsecrets(obj) 92 | 93 | 94 | def loads(s): 95 | obj = simplejson.loads(s) 96 | return _validate_clientsecrets(obj) 97 | 98 | 99 | def _loadfile(filename): 100 | try: 101 | fp = file(filename, 'r') 102 | try: 103 | obj = simplejson.load(fp) 104 | finally: 105 | fp.close() 106 | except IOError: 107 | raise InvalidClientSecretsError('File not found: "%s"' % filename) 108 | return _validate_clientsecrets(obj) 109 | 110 | 111 | def loadfile(filename, cache=None): 112 | """Loading of client_secrets JSON file, optionally backed by a cache. 113 | 114 | Typical cache storage would be App Engine memcache service, 115 | but you can pass in any other cache client that implements 116 | these methods: 117 | - get(key, namespace=ns) 118 | - set(key, value, namespace=ns) 119 | 120 | Usage: 121 | # without caching 122 | client_type, client_info = loadfile('secrets.json') 123 | # using App Engine memcache service 124 | from google.appengine.api import memcache 125 | client_type, client_info = loadfile('secrets.json', cache=memcache) 126 | 127 | Args: 128 | filename: string, Path to a client_secrets.json file on a filesystem. 129 | cache: An optional cache service client that implements get() and set() 130 | methods. If not specified, the file is always being loaded from 131 | a filesystem. 132 | 133 | Raises: 134 | InvalidClientSecretsError: In case of a validation error or some 135 | I/O failure. Can happen only on cache miss. 136 | 137 | Returns: 138 | (client_type, client_info) tuple, as _loadfile() normally would. 139 | JSON contents is validated only during first load. Cache hits are not 140 | validated. 141 | """ 142 | _SECRET_NAMESPACE = 'oauth2client:secrets#ns' 143 | 144 | if not cache: 145 | return _loadfile(filename) 146 | 147 | obj = cache.get(filename, namespace=_SECRET_NAMESPACE) 148 | if obj is None: 149 | client_type, client_info = _loadfile(filename) 150 | obj = {client_type: client_info} 151 | cache.set(filename, obj, namespace=_SECRET_NAMESPACE) 152 | 153 | return obj.iteritems().next() 154 | -------------------------------------------------------------------------------- /mirror_api_server/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2013 Gerwin Sturm, FoldedSoft e.U. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0# 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Configuration options and helper functions for all services""" 17 | 18 | __author__ = 'scarygami@gmail.com (Gerwin Sturm)' 19 | 20 | import jinja2 21 | import json 22 | import os 23 | import webapp2 24 | 25 | from apiclient.discovery import build 26 | from google.appengine.api.app_identity import get_application_id 27 | from google.appengine.ext import ndb 28 | from oauth2client.appengine import CredentialsNDBProperty 29 | from webapp2_extras import sessions 30 | from webapp2_extras.appengine import sessions_memcache 31 | 32 | JINJA = jinja2.Environment(loader=jinja2.FileSystemLoader(os.path.dirname(__file__))) 33 | 34 | appname = get_application_id() 35 | base_url = "https://" + appname + ".appspot.com" 36 | discovery_url = base_url + "/_ah/api" 37 | discovery_service_url = discovery_url + "/discovery/v1/apis/{api}/{apiVersion}/rest" 38 | 39 | with open("client_secrets.json", "r") as fh: 40 | secrets = json.load(fh)["web"] 41 | CLIENT_ID = secrets["client_id"] 42 | SESSION_KEY = str(secrets["session_secret"]) 43 | API_KEY = secrets["api_key"] 44 | 45 | config = {} 46 | config["webapp2_extras.sessions"] = {"secret_key": SESSION_KEY} 47 | 48 | # Add any additional scopes that you might need for your service to access other Google APIs 49 | COMMON_SCOPES = ["https://www.googleapis.com/auth/plus.login"] 50 | 51 | # userinfo.email scope is required to work with Google Cloud Endpoints 52 | TEST_SCOPES = ["https://www.googleapis.com/auth/userinfo.email"] 53 | 54 | # Remove the location scope from here if you don't need it 55 | REAL_SCOPES = [ 56 | "https://www.googleapis.com/auth/glass.timeline", 57 | "https://www.googleapis.com/auth/glass.location" 58 | ] 59 | 60 | # Requests for app activities during the Auth flow 61 | REQUEST_VISIBLE_ACTIONS = [ 62 | "http://schemas.google.com/CheckInActivity" 63 | ] 64 | 65 | 66 | def createError(code, message): 67 | """Create a JSON string to be returned as error response to requests""" 68 | return json.dumps({"error": {"code": code, "message": message}}) 69 | 70 | 71 | def createMessage(message): 72 | """Create a JSON string to be returned as response to requests""" 73 | return json.dumps({"message": message}) 74 | 75 | 76 | class BaseHandler(webapp2.RequestHandler): 77 | """Base request handler to enable session storage for all handlers""" 78 | 79 | def dispatch(self): 80 | # Get a session store for this request. 81 | self.session_store = sessions.get_store(request=self.request) 82 | 83 | try: 84 | # Dispatch the request. 85 | webapp2.RequestHandler.dispatch(self) 86 | finally: 87 | # Save all sessions. 88 | self.session_store.save_sessions(self.response) 89 | 90 | @webapp2.cached_property 91 | def session(self): 92 | return self.session_store.get_session(name='mirror_session', factory=sessions_memcache.MemcacheSessionFactory) 93 | 94 | 95 | def build_service_from_service(service, api, version): 96 | """Build a Google API service using another pre-authed service""" 97 | 98 | new_service = build(api, version, http=service._http) 99 | 100 | return new_service 101 | 102 | 103 | class User(ndb.Model): 104 | """Datastore model to keep all relevant information about a user 105 | 106 | Properties: 107 | displayName Name of the user as returned by the Google+ API 108 | imageUrl Avatar image of the user as returned by the Google+ API 109 | verifyToken Random token generated for each user to check validity of incoming notifications 110 | credentials OAuth2 Access and refresh token to be used for requests against the Mirror API 111 | latitude Latest recorded latitude of the user 112 | longitude Latest recorded longitude of the user 113 | locationUpdate DateTime at which the location of the user was last update 114 | friends List of Google+ friends id, as returned by the Google+ API 115 | """ 116 | 117 | displayName = ndb.StringProperty() 118 | imageUrl = ndb.StringProperty() 119 | verifyToken = ndb.StringProperty() 120 | credentials = CredentialsNDBProperty() 121 | latitude = ndb.FloatProperty() 122 | longitude = ndb.FloatProperty() 123 | locationUpdate = ndb.DateTimeProperty() 124 | friends = ndb.StringProperty(repeated=True) 125 | 126 | 127 | class TestUser(User): 128 | 129 | _testUser = True 130 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/lib/oauth2client/clientsecrets.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Utilities for reading OAuth 2.0 client secret files. 16 | 17 | A client_secrets.json file contains all the information needed to interact with 18 | an OAuth 2.0 protected service. 19 | """ 20 | 21 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 22 | 23 | 24 | from anyjson import simplejson 25 | 26 | # Properties that make a client_secrets.json file valid. 27 | TYPE_WEB = 'web' 28 | TYPE_INSTALLED = 'installed' 29 | 30 | VALID_CLIENT = { 31 | TYPE_WEB: { 32 | 'required': [ 33 | 'client_id', 34 | 'client_secret', 35 | 'redirect_uris', 36 | 'auth_uri', 37 | 'token_uri', 38 | ], 39 | 'string': [ 40 | 'client_id', 41 | 'client_secret', 42 | ], 43 | }, 44 | TYPE_INSTALLED: { 45 | 'required': [ 46 | 'client_id', 47 | 'client_secret', 48 | 'redirect_uris', 49 | 'auth_uri', 50 | 'token_uri', 51 | ], 52 | 'string': [ 53 | 'client_id', 54 | 'client_secret', 55 | ], 56 | }, 57 | } 58 | 59 | 60 | class Error(Exception): 61 | """Base error for this module.""" 62 | pass 63 | 64 | 65 | class InvalidClientSecretsError(Error): 66 | """Format of ClientSecrets file is invalid.""" 67 | pass 68 | 69 | 70 | def _validate_clientsecrets(obj): 71 | if obj is None or len(obj) != 1: 72 | raise InvalidClientSecretsError('Invalid file format.') 73 | client_type = obj.keys()[0] 74 | if client_type not in VALID_CLIENT.keys(): 75 | raise InvalidClientSecretsError('Unknown client type: %s.' % client_type) 76 | client_info = obj[client_type] 77 | for prop_name in VALID_CLIENT[client_type]['required']: 78 | if prop_name not in client_info: 79 | raise InvalidClientSecretsError( 80 | 'Missing property "%s" in a client type of "%s".' % (prop_name, 81 | client_type)) 82 | for prop_name in VALID_CLIENT[client_type]['string']: 83 | if client_info[prop_name].startswith('[['): 84 | raise InvalidClientSecretsError( 85 | 'Property "%s" is not configured.' % prop_name) 86 | return client_type, client_info 87 | 88 | 89 | def load(fp): 90 | obj = simplejson.load(fp) 91 | return _validate_clientsecrets(obj) 92 | 93 | 94 | def loads(s): 95 | obj = simplejson.loads(s) 96 | return _validate_clientsecrets(obj) 97 | 98 | 99 | def _loadfile(filename): 100 | try: 101 | fp = file(filename, 'r') 102 | try: 103 | obj = simplejson.load(fp) 104 | finally: 105 | fp.close() 106 | except IOError: 107 | raise InvalidClientSecretsError('File not found: "%s"' % filename) 108 | return _validate_clientsecrets(obj) 109 | 110 | 111 | def loadfile(filename, cache=None): 112 | """Loading of client_secrets JSON file, optionally backed by a cache. 113 | 114 | Typical cache storage would be App Engine memcache service, 115 | but you can pass in any other cache client that implements 116 | these methods: 117 | - get(key, namespace=ns) 118 | - set(key, value, namespace=ns) 119 | 120 | Usage: 121 | # without caching 122 | client_type, client_info = loadfile('secrets.json') 123 | # using App Engine memcache service 124 | from google.appengine.api import memcache 125 | client_type, client_info = loadfile('secrets.json', cache=memcache) 126 | 127 | Args: 128 | filename: string, Path to a client_secrets.json file on a filesystem. 129 | cache: An optional cache service client that implements get() and set() 130 | methods. If not specified, the file is always being loaded from 131 | a filesystem. 132 | 133 | Raises: 134 | InvalidClientSecretsError: In case of a validation error or some 135 | I/O failure. Can happen only on cache miss. 136 | 137 | Returns: 138 | (client_type, client_info) tuple, as _loadfile() normally would. 139 | JSON contents is validated only during first load. Cache hits are not 140 | validated. 141 | """ 142 | _SECRET_NAMESPACE = 'oauth2client:secrets#ns' 143 | 144 | if not cache: 145 | return _loadfile(filename) 146 | 147 | obj = cache.get(filename, namespace=_SECRET_NAMESPACE) 148 | if obj is None: 149 | client_type, client_info = _loadfile(filename) 150 | obj = {client_type: client_info} 151 | cache.set(filename, obj, namespace=_SECRET_NAMESPACE) 152 | 153 | return obj.iteritems().next() 154 | -------------------------------------------------------------------------------- /examples/hangout-comment-tracker/lib/oauth2client/clientsecrets.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Utilities for reading OAuth 2.0 client secret files. 16 | 17 | A client_secrets.json file contains all the information needed to interact with 18 | an OAuth 2.0 protected service. 19 | """ 20 | 21 | __author__ = 'jcgregorio@google.com (Joe Gregorio)' 22 | 23 | 24 | from anyjson import simplejson 25 | 26 | # Properties that make a client_secrets.json file valid. 27 | TYPE_WEB = 'web' 28 | TYPE_INSTALLED = 'installed' 29 | 30 | VALID_CLIENT = { 31 | TYPE_WEB: { 32 | 'required': [ 33 | 'client_id', 34 | 'client_secret', 35 | 'redirect_uris', 36 | 'auth_uri', 37 | 'token_uri', 38 | ], 39 | 'string': [ 40 | 'client_id', 41 | 'client_secret', 42 | ], 43 | }, 44 | TYPE_INSTALLED: { 45 | 'required': [ 46 | 'client_id', 47 | 'client_secret', 48 | 'redirect_uris', 49 | 'auth_uri', 50 | 'token_uri', 51 | ], 52 | 'string': [ 53 | 'client_id', 54 | 'client_secret', 55 | ], 56 | }, 57 | } 58 | 59 | 60 | class Error(Exception): 61 | """Base error for this module.""" 62 | pass 63 | 64 | 65 | class InvalidClientSecretsError(Error): 66 | """Format of ClientSecrets file is invalid.""" 67 | pass 68 | 69 | 70 | def _validate_clientsecrets(obj): 71 | if obj is None or len(obj) != 1: 72 | raise InvalidClientSecretsError('Invalid file format.') 73 | client_type = obj.keys()[0] 74 | if client_type not in VALID_CLIENT.keys(): 75 | raise InvalidClientSecretsError('Unknown client type: %s.' % client_type) 76 | client_info = obj[client_type] 77 | for prop_name in VALID_CLIENT[client_type]['required']: 78 | if prop_name not in client_info: 79 | raise InvalidClientSecretsError( 80 | 'Missing property "%s" in a client type of "%s".' % (prop_name, 81 | client_type)) 82 | for prop_name in VALID_CLIENT[client_type]['string']: 83 | if client_info[prop_name].startswith('[['): 84 | raise InvalidClientSecretsError( 85 | 'Property "%s" is not configured.' % prop_name) 86 | return client_type, client_info 87 | 88 | 89 | def load(fp): 90 | obj = simplejson.load(fp) 91 | return _validate_clientsecrets(obj) 92 | 93 | 94 | def loads(s): 95 | obj = simplejson.loads(s) 96 | return _validate_clientsecrets(obj) 97 | 98 | 99 | def _loadfile(filename): 100 | try: 101 | fp = file(filename, 'r') 102 | try: 103 | obj = simplejson.load(fp) 104 | finally: 105 | fp.close() 106 | except IOError: 107 | raise InvalidClientSecretsError('File not found: "%s"' % filename) 108 | return _validate_clientsecrets(obj) 109 | 110 | 111 | def loadfile(filename, cache=None): 112 | """Loading of client_secrets JSON file, optionally backed by a cache. 113 | 114 | Typical cache storage would be App Engine memcache service, 115 | but you can pass in any other cache client that implements 116 | these methods: 117 | - get(key, namespace=ns) 118 | - set(key, value, namespace=ns) 119 | 120 | Usage: 121 | # without caching 122 | client_type, client_info = loadfile('secrets.json') 123 | # using App Engine memcache service 124 | from google.appengine.api import memcache 125 | client_type, client_info = loadfile('secrets.json', cache=memcache) 126 | 127 | Args: 128 | filename: string, Path to a client_secrets.json file on a filesystem. 129 | cache: An optional cache service client that implements get() and set() 130 | methods. If not specified, the file is always being loaded from 131 | a filesystem. 132 | 133 | Raises: 134 | InvalidClientSecretsError: In case of a validation error or some 135 | I/O failure. Can happen only on cache miss. 136 | 137 | Returns: 138 | (client_type, client_info) tuple, as _loadfile() normally would. 139 | JSON contents is validated only during first load. Cache hits are not 140 | validated. 141 | """ 142 | _SECRET_NAMESPACE = 'oauth2client:secrets#ns' 143 | 144 | if not cache: 145 | return _loadfile(filename) 146 | 147 | obj = cache.get(filename, namespace=_SECRET_NAMESPACE) 148 | if obj is None: 149 | client_type, client_info = _loadfile(filename) 150 | obj = {client_type: client_info} 151 | cache.set(filename, obj, namespace=_SECRET_NAMESPACE) 152 | 153 | return obj.iteritems().next() 154 | -------------------------------------------------------------------------------- /mirror_api_server/lib/uritemplate/__init__.py: -------------------------------------------------------------------------------- 1 | # Early, and incomplete implementation of -04. 2 | # 3 | import re 4 | import urllib 5 | 6 | RESERVED = ":/?#[]@!$&'()*+,;=" 7 | OPERATOR = "+./;?|!@" 8 | EXPLODE = "*+" 9 | MODIFIER = ":^" 10 | TEMPLATE = re.compile(r"{(?P[\+\./;\?|!@])?(?P[^}]+)}", re.UNICODE) 11 | VAR = re.compile(r"^(?P[^=\+\*:\^]+)((?P[\+\*])|(?P[:\^]-?[0-9]+))?(=(?P.*))?$", re.UNICODE) 12 | 13 | def _tostring(varname, value, explode, operator, safe=""): 14 | if type(value) == type([]): 15 | if explode == "+": 16 | return ",".join([varname + "." + urllib.quote(x, safe) for x in value]) 17 | else: 18 | return ",".join([urllib.quote(x, safe) for x in value]) 19 | if type(value) == type({}): 20 | keys = value.keys() 21 | keys.sort() 22 | if explode == "+": 23 | return ",".join([varname + "." + urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys]) 24 | else: 25 | return ",".join([urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys]) 26 | else: 27 | return urllib.quote(value, safe) 28 | 29 | 30 | def _tostring_path(varname, value, explode, operator, safe=""): 31 | joiner = operator 32 | if type(value) == type([]): 33 | if explode == "+": 34 | return joiner.join([varname + "." + urllib.quote(x, safe) for x in value]) 35 | elif explode == "*": 36 | return joiner.join([urllib.quote(x, safe) for x in value]) 37 | else: 38 | return ",".join([urllib.quote(x, safe) for x in value]) 39 | elif type(value) == type({}): 40 | keys = value.keys() 41 | keys.sort() 42 | if explode == "+": 43 | return joiner.join([varname + "." + urllib.quote(key, safe) + joiner + urllib.quote(value[key], safe) for key in keys]) 44 | elif explode == "*": 45 | return joiner.join([urllib.quote(key, safe) + joiner + urllib.quote(value[key], safe) for key in keys]) 46 | else: 47 | return ",".join([urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys]) 48 | else: 49 | if value: 50 | return urllib.quote(value, safe) 51 | else: 52 | return "" 53 | 54 | def _tostring_query(varname, value, explode, operator, safe=""): 55 | joiner = operator 56 | varprefix = "" 57 | if operator == "?": 58 | joiner = "&" 59 | varprefix = varname + "=" 60 | if type(value) == type([]): 61 | if 0 == len(value): 62 | return "" 63 | if explode == "+": 64 | return joiner.join([varname + "=" + urllib.quote(x, safe) for x in value]) 65 | elif explode == "*": 66 | return joiner.join([urllib.quote(x, safe) for x in value]) 67 | else: 68 | return varprefix + ",".join([urllib.quote(x, safe) for x in value]) 69 | elif type(value) == type({}): 70 | if 0 == len(value): 71 | return "" 72 | keys = value.keys() 73 | keys.sort() 74 | if explode == "+": 75 | return joiner.join([varname + "." + urllib.quote(key, safe) + "=" + urllib.quote(value[key], safe) for key in keys]) 76 | elif explode == "*": 77 | return joiner.join([urllib.quote(key, safe) + "=" + urllib.quote(value[key], safe) for key in keys]) 78 | else: 79 | return varprefix + ",".join([urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys]) 80 | else: 81 | if value: 82 | return varname + "=" + urllib.quote(value, safe) 83 | else: 84 | return varname 85 | 86 | TOSTRING = { 87 | "" : _tostring, 88 | "+": _tostring, 89 | ";": _tostring_query, 90 | "?": _tostring_query, 91 | "/": _tostring_path, 92 | ".": _tostring_path, 93 | } 94 | 95 | 96 | def expand(template, vars): 97 | def _sub(match): 98 | groupdict = match.groupdict() 99 | operator = groupdict.get('operator') 100 | if operator is None: 101 | operator = '' 102 | varlist = groupdict.get('varlist') 103 | 104 | safe = "@" 105 | if operator == '+': 106 | safe = RESERVED 107 | varspecs = varlist.split(",") 108 | varnames = [] 109 | defaults = {} 110 | for varspec in varspecs: 111 | m = VAR.search(varspec) 112 | groupdict = m.groupdict() 113 | varname = groupdict.get('varname') 114 | explode = groupdict.get('explode') 115 | partial = groupdict.get('partial') 116 | default = groupdict.get('default') 117 | if default: 118 | defaults[varname] = default 119 | varnames.append((varname, explode, partial)) 120 | 121 | retval = [] 122 | joiner = operator 123 | prefix = operator 124 | if operator == "+": 125 | prefix = "" 126 | joiner = "," 127 | if operator == "?": 128 | joiner = "&" 129 | if operator == "": 130 | joiner = "," 131 | for varname, explode, partial in varnames: 132 | if varname in vars: 133 | value = vars[varname] 134 | #if not value and (type(value) == type({}) or type(value) == type([])) and varname in defaults: 135 | if not value and value != "" and varname in defaults: 136 | value = defaults[varname] 137 | elif varname in defaults: 138 | value = defaults[varname] 139 | else: 140 | continue 141 | retval.append(TOSTRING[operator](varname, value, explode, operator, safe=safe)) 142 | if "".join(retval): 143 | return prefix + joiner.join(retval) 144 | else: 145 | return "" 146 | 147 | return TEMPLATE.sub(_sub, template) 148 | -------------------------------------------------------------------------------- /examples/colours-of-the-world/lib/uritemplate/__init__.py: -------------------------------------------------------------------------------- 1 | # Early, and incomplete implementation of -04. 2 | # 3 | import re 4 | import urllib 5 | 6 | RESERVED = ":/?#[]@!$&'()*+,;=" 7 | OPERATOR = "+./;?|!@" 8 | EXPLODE = "*+" 9 | MODIFIER = ":^" 10 | TEMPLATE = re.compile(r"{(?P[\+\./;\?|!@])?(?P[^}]+)}", re.UNICODE) 11 | VAR = re.compile(r"^(?P[^=\+\*:\^]+)((?P[\+\*])|(?P[:\^]-?[0-9]+))?(=(?P.*))?$", re.UNICODE) 12 | 13 | def _tostring(varname, value, explode, operator, safe=""): 14 | if type(value) == type([]): 15 | if explode == "+": 16 | return ",".join([varname + "." + urllib.quote(x, safe) for x in value]) 17 | else: 18 | return ",".join([urllib.quote(x, safe) for x in value]) 19 | if type(value) == type({}): 20 | keys = value.keys() 21 | keys.sort() 22 | if explode == "+": 23 | return ",".join([varname + "." + urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys]) 24 | else: 25 | return ",".join([urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys]) 26 | else: 27 | return urllib.quote(value, safe) 28 | 29 | 30 | def _tostring_path(varname, value, explode, operator, safe=""): 31 | joiner = operator 32 | if type(value) == type([]): 33 | if explode == "+": 34 | return joiner.join([varname + "." + urllib.quote(x, safe) for x in value]) 35 | elif explode == "*": 36 | return joiner.join([urllib.quote(x, safe) for x in value]) 37 | else: 38 | return ",".join([urllib.quote(x, safe) for x in value]) 39 | elif type(value) == type({}): 40 | keys = value.keys() 41 | keys.sort() 42 | if explode == "+": 43 | return joiner.join([varname + "." + urllib.quote(key, safe) + joiner + urllib.quote(value[key], safe) for key in keys]) 44 | elif explode == "*": 45 | return joiner.join([urllib.quote(key, safe) + joiner + urllib.quote(value[key], safe) for key in keys]) 46 | else: 47 | return ",".join([urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys]) 48 | else: 49 | if value: 50 | return urllib.quote(value, safe) 51 | else: 52 | return "" 53 | 54 | def _tostring_query(varname, value, explode, operator, safe=""): 55 | joiner = operator 56 | varprefix = "" 57 | if operator == "?": 58 | joiner = "&" 59 | varprefix = varname + "=" 60 | if type(value) == type([]): 61 | if 0 == len(value): 62 | return "" 63 | if explode == "+": 64 | return joiner.join([varname + "=" + urllib.quote(x, safe) for x in value]) 65 | elif explode == "*": 66 | return joiner.join([urllib.quote(x, safe) for x in value]) 67 | else: 68 | return varprefix + ",".join([urllib.quote(x, safe) for x in value]) 69 | elif type(value) == type({}): 70 | if 0 == len(value): 71 | return "" 72 | keys = value.keys() 73 | keys.sort() 74 | if explode == "+": 75 | return joiner.join([varname + "." + urllib.quote(key, safe) + "=" + urllib.quote(value[key], safe) for key in keys]) 76 | elif explode == "*": 77 | return joiner.join([urllib.quote(key, safe) + "=" + urllib.quote(value[key], safe) for key in keys]) 78 | else: 79 | return varprefix + ",".join([urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys]) 80 | else: 81 | if value: 82 | return varname + "=" + urllib.quote(value, safe) 83 | else: 84 | return varname 85 | 86 | TOSTRING = { 87 | "" : _tostring, 88 | "+": _tostring, 89 | ";": _tostring_query, 90 | "?": _tostring_query, 91 | "/": _tostring_path, 92 | ".": _tostring_path, 93 | } 94 | 95 | 96 | def expand(template, vars): 97 | def _sub(match): 98 | groupdict = match.groupdict() 99 | operator = groupdict.get('operator') 100 | if operator is None: 101 | operator = '' 102 | varlist = groupdict.get('varlist') 103 | 104 | safe = "@" 105 | if operator == '+': 106 | safe = RESERVED 107 | varspecs = varlist.split(",") 108 | varnames = [] 109 | defaults = {} 110 | for varspec in varspecs: 111 | m = VAR.search(varspec) 112 | groupdict = m.groupdict() 113 | varname = groupdict.get('varname') 114 | explode = groupdict.get('explode') 115 | partial = groupdict.get('partial') 116 | default = groupdict.get('default') 117 | if default: 118 | defaults[varname] = default 119 | varnames.append((varname, explode, partial)) 120 | 121 | retval = [] 122 | joiner = operator 123 | prefix = operator 124 | if operator == "+": 125 | prefix = "" 126 | joiner = "," 127 | if operator == "?": 128 | joiner = "&" 129 | if operator == "": 130 | joiner = "," 131 | for varname, explode, partial in varnames: 132 | if varname in vars: 133 | value = vars[varname] 134 | #if not value and (type(value) == type({}) or type(value) == type([])) and varname in defaults: 135 | if not value and value != "" and varname in defaults: 136 | value = defaults[varname] 137 | elif varname in defaults: 138 | value = defaults[varname] 139 | else: 140 | continue 141 | retval.append(TOSTRING[operator](varname, value, explode, operator, safe=safe)) 142 | if "".join(retval): 143 | return prefix + joiner.join(retval) 144 | else: 145 | return "" 146 | 147 | return TEMPLATE.sub(_sub, template) 148 | --------------------------------------------------------------------------------