Upgrade to oauth2client 2.0.1. Remove support for seperate pem keys.

This commit is contained in:
Jay Lee 2016-03-16 12:53:05 -04:00
parent 1d30eb7d91
commit d8a78d96ae
39 changed files with 7907 additions and 846 deletions

BIN
src/GoogleUpdate.adm Normal file

Binary file not shown.

5453
src/fakeusers.newer Normal file

File diff suppressed because it is too large Load Diff

View File

@ -37,6 +37,7 @@ import googleapiclient.discovery
import googleapiclient.errors
import googleapiclient.http
import oauth2client.client
import oauth2client.service_account
import oauth2client.file
import oauth2client.tools
import mimetypes
@ -847,25 +848,11 @@ def buildGAPIServiceObject(api, act_as, soft_errors=False):
printLine(GAM_WIKI_CREATE_CLIENT_SECRETS)
systemErrorExit(6, None)
json_data = json.loads(json_string)
try:
# new format with config and key in the .json file...
GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_EMAIL] = json_data[u'client_email']
GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID] = json_data[u'client_id']
GM_Globals[GM_OAUTH2SERVICE_KEY] = json_data[u'private_key']
except KeyError:
try:
# old format with config in the .json file and key in the .p12 file...
GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_EMAIL] = json_data[u'web'][u'client_email']
GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID] = json_data[u'web'][u'client_id']
GM_Globals[GM_OAUTH2SERVICE_KEY] = readFile(GC_Values[GC_OAUTH2SERVICE_JSON].replace(u'.json', u'.p12'))
except KeyError:
printLine(MESSAGE_WIKI_INSTRUCTIONS_OAUTH2SERVICE_JSON)
printLine(GAM_WIKI_CREATE_CLIENT_SECRETS)
systemErrorExit(17, MESSAGE_OAUTH2SERVICE_JSON_INVALID.format(GC_Values[GC_OAUTH2SERVICE_JSON]))
scope = getAPIScope(api)
credentials = oauth2client.client.SignedJwtAssertionCredentials(GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_EMAIL],
GM_Globals[GM_OAUTH2SERVICE_KEY],
scope=scope, user_agent=GAM_INFO, sub=act_as)
scopes = getAPIScope(api)
credentials = oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_dict(
json_data, scopes)
credentials = credentials.create_delegated(act_as)
credentials.user_agent = GAM_INFO
http = credentials.authorize(httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL],
cache=GC_Values[GC_CACHE_DIR]))
version = getAPIVer(api)

375
src/gamtest.csv Normal file
View File

@ -0,0 +1,375 @@
AlaneEA@LeeSchools.Net
superintendent@leeschools.net
AdministrativeMeetings@LeeSchools.net
EllenMA@LeeSchools.Net
TonyAAl@LeeSchools.net
CandaceMA@LeeSchools.Net
Dwayne.Alton@leeschools.net
MichaelLA@LeeSchools.net
RondaKa@LeeSchools.Net
ChevoneeseA@LeeSchools.net
RebeccaAn@LeeSchools.net
DaveJA@LeeSchools.net
BenjaminIA@LeeSchools.Net
AliceEB@LeeSchools.net
RichardDB@LeeSchools.Net
KimberlyBB@LeeSchools.net
TroyAB@LeeSchools.net
KevinB@LeeSchools.net
DornB@LeeSchools.net
PaulaMB@LeeSchools.Net
MauraGB@LeeSchools.Net
LindaKBe@leeschools.net
MistyJB@LeeSchools.Net
DanaAB@LeeSchools.net
AndrewMB@LeeSchools.Net
MaryABl@LeeSchools.net
DwayneEB@LeeSchools.net
AmyCB@LeeSchools.Net
TaraLB@LeeSchools.net
GeorgePB@leeschools.net
StephanieTB@LeeSchools.net
MelissaRB@LeeSchools.net
DeniseKB@LeeSchools.Net
TheresaMB@LeeSchools.Net
TommyLB@LeeSchools.Net
LindaKB@LeeSchools.Net
MatthewTB@LeeSchools.net
LoriMB@LeeSchools.Net
KimberlyAB@LeeSchools.Net
MonicaTB@LeeSchools.Net
JamiMB@LeeSchools.Net
AndrewSB@LeeSchools.net
RobertGB@leeschools.net
RebeccaAB@LeeSchools.Net
JamesLB@LeeSchools.Net
LindaEBu@LeeSchools.Net
KristinTB@LeeSchools.Net
MarshaAB@LeeSchools.Net
DaveBu@LeeSchools.net
GeorgeKB@LeeSchools.Net
CarlCB@LeeSchools.Net
ChristopherSB@LeeSchools.net
MikeCB@LeeSchools.Net
ChristineRB@LeeSchools.Net
RobertLB@LeeSchools.Net
LeighLB@LeeSchools.Net
BrianFB@LeeSchools.net
ChristopheRC@LeeSchools.Net
TeriLC@LeeSchools.Net
LindaMCa@LeeSchools.Net
SusanLC@LeeSchools.Net
DeniseMC@LeeSchools.net
LorieAC@LeeSchools.Net
MikeSC@LeeSchools.Net
AnikaBC@leeschools.net
EdwinLC@LeeSchools.Net
BarbaraC@LeeSchools.net
AmityAC@LeeSchools.net
CyndiAC@LeeSchools.Net
ChristineCh@LeeSchools.Net
JenniferMCo@LeeSchools.net
RobertLC@LeeSchools.net
AnnFC@LeeSchools.net
KimberlyACo@leeschools.net
ElizabethACo@LeeSchools.Net
ScottGC@LeeSchools.Net
RobertFCo@LeeSchools.Net
JackieLC@LeeSchools.Net
MichelleCo@LeeSchools.Net
LaurenCCo@LeeSchools.net
DwayneMC@LeeSchools.Net
JamiDC@LeeSchools.net
MarilynJC@leeschools.net
BrianLC@LeeSchools.Net
AaronBC@LeeSchools.Net
HelenJD@LeeSchools.net
KennaJD@leeschools.net
GeraldBD@LeeSchools.Net
AllisonAD@LeeSchools.net
AmiVD@LeeSchools.Net
JenniferLD@LeeSchools.net
LetaDD@LeeSchools.Net
DebbieLDi@LeeSchools.Net
JeffHD@LeeSchools.Net
KennethDD@LeeSchools.net
RobertDo@LeeSchools.net
KatieAD@LeeSchools.Net
JohnWD@LeeSchools.Net
JessicaKD@LeeSchools.Net
JuleeAD@LeeSchools.Net
AlexJD@LeeSchools.Net
LisaDE@LeeSchools.Net
BrianE@LeeSchools.net
TrentHE@LeeSchools.Net
LynnME@LeeSchools.Net
JenniferAE@LeeSchools.Net
RitaEE@LeeSchools.net
DarrylEE@LeeSchools.Net
PattiDE@LeeSchools.Net
ChristianJE@LeeSchools.Net
DamanJE@LeeSchools.Net
JeffreyBE@LeeSchools.Net
AngelaE@LeeSchools.net
ToddGE@LeeSchools.net
AnnWF@LeeSchools.Net
GinaVF@LeeSchools.net
ElizabethFF@LeeSchools.Net
JillianLF@LeeSchools.net
DeniseCF@LeeSchools.Net
ShannaMF@LeeSchools.Net
JamesLF@LeeSchools.net
JeananneVF@LeeSchools.Net
TammyWF@LeeSchools.Net
MichelleHF@LeeSchools.Net
RobinRF@LeeSchools.Net
CarolAF@LeeSchools.Net
GwendelynYF@LeeSchools.net
MichaelRGa@LeeSchools.Net
ClintonRG@LeeSchools.net
NenaSG@LeeSchools.Net
BethAGe@LeeSchools.Net
LawrenceMG@LeeSchools.Net
CherryMG@LeeSchools.net
BrianLG@LeeSchools.net
DrVickiLG@LeeSchools.net
MarthaDG@LeeSchools.Net
RachelGO@LeeSchools.Net
NancyJG@LeeSchools.net
MaryBethBG@LeeSchools.Net
MonicaLG@LeeSchools.net
ErinBG@LeeSchools.Net
SusannG@LeeSchools.net
DaryaNG@LeeSchools.Net
TanyaVG@LeeSchools.net
LeslieAG@LeeSchools.Net
AndreaLG@LeeSchools.net
VivianCG@LeeSchools.Net
DeadraLH@LeeSchools.net
ShellieSH@LeeSchools.Net
AndrewJH@LeeSchools.net
StephenLH@LeeSchools.Net
KariAHa@leeschools.net
ToniLHa@LeeSchools.Net
JohnnieMaeH@LeeSchools.net
MarthaKH@LeeSchools.net
PatrickJH@LeeSchools.Net
BrandonJH@LeeSchools.net
CynthiaMHe@LeeSchools.Net
LeonaLH@LeeSchools.Net
DonnieLH@LeeSchools.Net
LoriMH@LeeSchools.Net
DaleMH@LeeSchools.Net
JimHOW@LeeSchools.Net
BelindaJH@LeeSchools.Net
DawnMHu@LeeSchools.net
LeoPH@LeeSchools.Net
JamesMI@LeeSchools.Net
MarthaBI@LeeSchools.net
RichardJI@LeeSchools.Net
RyanJJ@LeeSchools.net
SusanMJ@LeeSchools.Net
JillEJ@LeeSchools.Net
SusanLJ@LeeSchools.Net
ArleneSK@LeeSchools.Net
JanetLKe@LeeSchools.Net
ShelleyAK@LeeSchools.Net
FranK@LeeSchools.Net
TerriMK@LeeSchools.net
JamieKi@LeeSchools.net
TonyaFK@LeeSchools.Net
TimothyEK@LeeSchools.net
AdamJK@LeeSchools.net
JasonWKu@LeeSchools.net
ChristyMK@LeeSchools.net
ManeLa@LeeSchools.Net
JeanneJL@LeeSchools.Net
AshleyAL@LeeSchools.net
DavidPL@LeeSchools.Net
MelissaAL@LeeSchools.Net
ElijahTL@LeeSchools.Net
ScottLE@LeeSchools.net
TamiML@LeeSchools.net
NicoleLLe@LeeSchools.net
KellyLE@LeeSchools.Net
MichelleZL@LeeSchools.net
RogerWL@LeeSchools.Net
RuthieLL@LeeSchools.Net
DianaJL@LeeSchools.Net
EllenCL@LeeSchools.Net
CharlesLU@LeeSchools.Net
BrandyAM@LeeSchools.Net
TomLM@LeeSchools.Net
LindaMMA@LeeSchools.net
SusanMMa@LeeSchools.net
BrianMM@LeeSchools.Net
KellyAMa@LeeSchools.Net
KarenCM@LeeSchools.Net
ShelleyMA@LeeSchools.Net
JoyRM@leeschools.net
KeithBM@LeeSchools.Net
TamikaRM@LeeSchools.Net
EdwardAM@LeeSchools.Net
RobertWM@LeeSchools.Net
JeniferMM@LeeSchools.net
CindyKM@LeeSchools.Net
BillBM@LeeSchools.Net
JeffFM@LeeSchools.Net
GeorgeM@LeeSchools.net
NancyAM@LeeSchools.Net
BonnieMc@LeeSchools.net
DouglasIM@LeeSchools.Net
AngelTM@LeeSchools.net
MatthewCM@LeeSchools.Net
MichelleSM@LeeSchools.Net
StephanieME@LeeSchools.Net
ThomasJM@LeeSchools.Net
MatthewRMi@LeeSchools.Net
ThomasCM@LeeSchools.Net
JenniferLMi@leeschools.net
JenniferJM@LeeSchools.net
SamoneLM@LeeSchools.Net
KimberleyAMo@LeeSchools.net
RaniceM@LeeSchools.Net
JodyJM@LeeSchools.net
MarcBM@LeeSchools.Net
ObedNM@LeeSchools.Net
JamesWMo@LeeSchools.net
RobertVM@LeeSchools.Net
JacksonCM@LeeSchools.net
RobertM@LeeSchools.Net
ElysaM@LeeSchools.net
LisaJM@LeeSchools.Net
ShaneEM@LeeSchools.Net
LeilaMM@LeeSchools.Net
AngelaMN@LeeSchools.Net
CarlosLN@LeeSchools.Net
OscarOl@LeeSchools.net
LauraNO@LeeSchools.Net
NicoleDO@LeeSchools.Net
RosaMP@LeeSchools.net
DouglasCP@LeeSchools.Net
HeatherVP@LeeSchools.net
RichardWP@LeeSchools.Net
JasonLP@LeeSchools.Net
SusieLP@LeeSchools.net
JohnMP@LeeSchools.Net
DeniseDP@LeeSchools.Net
JosephTP@LeeSchools.Net
MitchellDPl@LeeSchools.net
JasonBP@LeeSchools.Net
AmandaMP@LeeSchools.net
BrianTP@LeeSchools.net
AngelaJP@LeeSchools.Net
BethanyLQ@LeeSchools.net
SorettaER@LeeSchools.net
VictoriaTR@leeschools.net
VirginiaRa@LeeSchools.net
ScottCR@LeeSchools.Net
JamieBR@LeeSchools.Net
MaryER@LeeSchools.Net
KristinaLR@leeschools.net
JosephMR@LeeSchools.net
GingerLR@LeeSchools.Net
EvelynRI@LeeSchools.Net
BrianWR@LeeSchools.net
MelissaSR@LeeSchools.Net
MicheleRO@LeeSchools.Net
MaryLynnR@LeeSchools.net
AngelaRR@LeeSchools.Net
BrianneMR@LeeSchools.net
AndreaHR@LeeSchools.net
JohnCRo@LeeSchools.Net
WilliamBR@LeeSchools.net
AidaCS@LeeSchools.Net
DianeTS@LeeSchools.Net
LindaJSa@LeeSchools.Net
JeffLSa@LeeSchools.Net
AmandaLSa@LeeSchools.net
DavidSan@LeeSchools.net
MarkAS@LeeSchools.Net
DouglasOS@LeeSchools.Net
KennethAS@LeeSchools.Net
RebeccaJS@LeeSchools.net
AndreaS@LeeSchools.Net
DonnaPS@LeeSchools.Net
RonaldKS@LeeSchools.net
CathyMS@LeeSchools.net
TammyMS@LeeSchools.net
CatherineHSc@leeschools.net
JenniferAS@leeschools.net
JenniferJS@LeeSchools.net
NathanS@LeeSchools.net
ValerieES@LeeSchools.Net
DianeMS@LeeSchools.Net
AlS@LeeSchools.Net
JenniferIS@LeeSchools.Net
JamesDS@LeeSchools.Net
ChrisMS@LeeSchools.Net
CindyFS@LeeSchools.Net
TinaLSil@LeeSchools.net
JasonSSi@LeeSchools.net
ClaytonDS@LeeSchools.Net
PeggyLS@leeschools.net
LisaS@LeeSchools.net
ShannonES@LeeSchools.Net
hollylsm@LeeSchools.Net
JenniferMSn@LeeSchools.Net
ReginaldHS@leeschools.net
MarlenS@LeeSchools.Net
KarenSSp@LeeSchools.net
JeffSS@LeeSchools.Net
LauraMS@LeeSchools.Net
MicheleASt@LeeSchools.net
LauraRSt@LeeSchools.Net
CayceLS@LeeSchools.Net
KellyAST@LeeSchools.Net
MikeSt@LeeSchools.net
SonnyAS@LeeSchools.Net
MaggieMS@LeeSchools.Net
AmyS@LeeSchools.net
CrissyWS@LeeSchools.Net
RobLS@LeeSchools.Net
KristineASu@LeeSchools.Net
ShellieDT@LeeSchools.Net
KellyVT@LeeSchools.net
MathewJT@LeeSchools.net
MirtaST@LeeSchools.Net
DonaldWT@LeeSchools.Net
CheriseWT@LeeSchools.Net
MelixsaT@LeeSchools.Net
LauraAT@LeeSchools.Net
WilliamBT@LeeSchools.net
JackieBT@LeeSchools.Net
KimberlyAV@LeeSchools.Net
MaraKV@LeeSchools.Net
ElizabethAVi@LeeSchools.net
CharlesRV@LeeSchools.net
BarbaraVO@LeeSchools.Net
BarbaraAWa@LeeSchools.Net
ToniMW@LeeSchools.Net
ForrestWa@LeeSchools.Net
KennethCW@LeeSchools.Net
EricLW@LeeSchools.Net
AmyEW@LeeSchools.net
NeketaCW@LeeSchools.Net
PadraicFW@LeeSchools.net
RobertaAW@LeeSchools.Net
DottieJW@LeeSchools.Net
JenniferKW@LeeSchools.net
HermanDW@LeeSchools.Net
ErinRW@LeeSchools.Net
JoeWil@LeeSchools.net
JeremeEW@LeeSchools.Net
EmmaMW@LeeSchools.net
StaciaAW@LeeSchools.net
SherriMW@LeeSchools.Net
CarolAW@LeeSchools.Net
KarenLWo@LeeSchools.Net
LarryNW@LeeSchools.Net
WinstonW@LeeSchools.net
LisaMWr@leeschools.net
MarlaSW@LeeSchools.Net
RitaAZ@LeeSchools.net
SusanKZ@LeeSchools.Net
TraceySZ@LeeSchools.Net
1 AlaneEA LeeSchools.Net
2 superintendent leeschools.net
3 AdministrativeMeetings LeeSchools.net
4 EllenMA LeeSchools.Net
5 TonyAAl LeeSchools.net
6 CandaceMA LeeSchools.Net
7 Dwayne.Alton leeschools.net
8 MichaelLA LeeSchools.net
9 RondaKa LeeSchools.Net
10 ChevoneeseA LeeSchools.net
11 RebeccaAn LeeSchools.net
12 DaveJA LeeSchools.net
13 BenjaminIA LeeSchools.Net
14 AliceEB LeeSchools.net
15 RichardDB LeeSchools.Net
16 KimberlyBB LeeSchools.net
17 TroyAB LeeSchools.net
18 KevinB LeeSchools.net
19 DornB LeeSchools.net
20 PaulaMB LeeSchools.Net
21 MauraGB LeeSchools.Net
22 LindaKBe leeschools.net
23 MistyJB LeeSchools.Net
24 DanaAB LeeSchools.net
25 AndrewMB LeeSchools.Net
26 MaryABl LeeSchools.net
27 DwayneEB LeeSchools.net
28 AmyCB LeeSchools.Net
29 TaraLB LeeSchools.net
30 GeorgePB leeschools.net
31 StephanieTB LeeSchools.net
32 MelissaRB LeeSchools.net
33 DeniseKB LeeSchools.Net
34 TheresaMB LeeSchools.Net
35 TommyLB LeeSchools.Net
36 LindaKB LeeSchools.Net
37 MatthewTB LeeSchools.net
38 LoriMB LeeSchools.Net
39 KimberlyAB LeeSchools.Net
40 MonicaTB LeeSchools.Net
41 JamiMB LeeSchools.Net
42 AndrewSB LeeSchools.net
43 RobertGB leeschools.net
44 RebeccaAB LeeSchools.Net
45 JamesLB LeeSchools.Net
46 LindaEBu LeeSchools.Net
47 KristinTB LeeSchools.Net
48 MarshaAB LeeSchools.Net
49 DaveBu LeeSchools.net
50 GeorgeKB LeeSchools.Net
51 CarlCB LeeSchools.Net
52 ChristopherSB LeeSchools.net
53 MikeCB LeeSchools.Net
54 ChristineRB LeeSchools.Net
55 RobertLB LeeSchools.Net
56 LeighLB LeeSchools.Net
57 BrianFB LeeSchools.net
58 ChristopheRC LeeSchools.Net
59 TeriLC LeeSchools.Net
60 LindaMCa LeeSchools.Net
61 SusanLC LeeSchools.Net
62 DeniseMC LeeSchools.net
63 LorieAC LeeSchools.Net
64 MikeSC LeeSchools.Net
65 AnikaBC leeschools.net
66 EdwinLC LeeSchools.Net
67 BarbaraC LeeSchools.net
68 AmityAC LeeSchools.net
69 CyndiAC LeeSchools.Net
70 ChristineCh LeeSchools.Net
71 JenniferMCo LeeSchools.net
72 RobertLC LeeSchools.net
73 AnnFC LeeSchools.net
74 KimberlyACo leeschools.net
75 ElizabethACo LeeSchools.Net
76 ScottGC LeeSchools.Net
77 RobertFCo LeeSchools.Net
78 JackieLC LeeSchools.Net
79 MichelleCo LeeSchools.Net
80 LaurenCCo LeeSchools.net
81 DwayneMC LeeSchools.Net
82 JamiDC LeeSchools.net
83 MarilynJC leeschools.net
84 BrianLC LeeSchools.Net
85 AaronBC LeeSchools.Net
86 HelenJD LeeSchools.net
87 KennaJD leeschools.net
88 GeraldBD LeeSchools.Net
89 AllisonAD LeeSchools.net
90 AmiVD LeeSchools.Net
91 JenniferLD LeeSchools.net
92 LetaDD LeeSchools.Net
93 DebbieLDi LeeSchools.Net
94 JeffHD LeeSchools.Net
95 KennethDD LeeSchools.net
96 RobertDo LeeSchools.net
97 KatieAD LeeSchools.Net
98 JohnWD LeeSchools.Net
99 JessicaKD LeeSchools.Net
100 JuleeAD LeeSchools.Net
101 AlexJD LeeSchools.Net
102 LisaDE LeeSchools.Net
103 BrianE LeeSchools.net
104 TrentHE LeeSchools.Net
105 LynnME LeeSchools.Net
106 JenniferAE LeeSchools.Net
107 RitaEE LeeSchools.net
108 DarrylEE LeeSchools.Net
109 PattiDE LeeSchools.Net
110 ChristianJE LeeSchools.Net
111 DamanJE LeeSchools.Net
112 JeffreyBE LeeSchools.Net
113 AngelaE LeeSchools.net
114 ToddGE LeeSchools.net
115 AnnWF LeeSchools.Net
116 GinaVF LeeSchools.net
117 ElizabethFF LeeSchools.Net
118 JillianLF LeeSchools.net
119 DeniseCF LeeSchools.Net
120 ShannaMF LeeSchools.Net
121 JamesLF LeeSchools.net
122 JeananneVF LeeSchools.Net
123 TammyWF LeeSchools.Net
124 MichelleHF LeeSchools.Net
125 RobinRF LeeSchools.Net
126 CarolAF LeeSchools.Net
127 GwendelynYF LeeSchools.net
128 MichaelRGa LeeSchools.Net
129 ClintonRG LeeSchools.net
130 NenaSG LeeSchools.Net
131 BethAGe LeeSchools.Net
132 LawrenceMG LeeSchools.Net
133 CherryMG LeeSchools.net
134 BrianLG LeeSchools.net
135 DrVickiLG LeeSchools.net
136 MarthaDG LeeSchools.Net
137 RachelGO LeeSchools.Net
138 NancyJG LeeSchools.net
139 MaryBethBG LeeSchools.Net
140 MonicaLG LeeSchools.net
141 ErinBG LeeSchools.Net
142 SusannG LeeSchools.net
143 DaryaNG LeeSchools.Net
144 TanyaVG LeeSchools.net
145 LeslieAG LeeSchools.Net
146 AndreaLG LeeSchools.net
147 VivianCG LeeSchools.Net
148 DeadraLH LeeSchools.net
149 ShellieSH LeeSchools.Net
150 AndrewJH LeeSchools.net
151 StephenLH LeeSchools.Net
152 KariAHa leeschools.net
153 ToniLHa LeeSchools.Net
154 JohnnieMaeH LeeSchools.net
155 MarthaKH LeeSchools.net
156 PatrickJH LeeSchools.Net
157 BrandonJH LeeSchools.net
158 CynthiaMHe LeeSchools.Net
159 LeonaLH LeeSchools.Net
160 DonnieLH LeeSchools.Net
161 LoriMH LeeSchools.Net
162 DaleMH LeeSchools.Net
163 JimHOW LeeSchools.Net
164 BelindaJH LeeSchools.Net
165 DawnMHu LeeSchools.net
166 LeoPH LeeSchools.Net
167 JamesMI LeeSchools.Net
168 MarthaBI LeeSchools.net
169 RichardJI LeeSchools.Net
170 RyanJJ LeeSchools.net
171 SusanMJ LeeSchools.Net
172 JillEJ LeeSchools.Net
173 SusanLJ LeeSchools.Net
174 ArleneSK LeeSchools.Net
175 JanetLKe LeeSchools.Net
176 ShelleyAK LeeSchools.Net
177 FranK LeeSchools.Net
178 TerriMK LeeSchools.net
179 JamieKi LeeSchools.net
180 TonyaFK LeeSchools.Net
181 TimothyEK LeeSchools.net
182 AdamJK LeeSchools.net
183 JasonWKu LeeSchools.net
184 ChristyMK LeeSchools.net
185 ManeLa LeeSchools.Net
186 JeanneJL LeeSchools.Net
187 AshleyAL LeeSchools.net
188 DavidPL LeeSchools.Net
189 MelissaAL LeeSchools.Net
190 ElijahTL LeeSchools.Net
191 ScottLE LeeSchools.net
192 TamiML LeeSchools.net
193 NicoleLLe LeeSchools.net
194 KellyLE LeeSchools.Net
195 MichelleZL LeeSchools.net
196 RogerWL LeeSchools.Net
197 RuthieLL LeeSchools.Net
198 DianaJL LeeSchools.Net
199 EllenCL LeeSchools.Net
200 CharlesLU LeeSchools.Net
201 BrandyAM LeeSchools.Net
202 TomLM LeeSchools.Net
203 LindaMMA LeeSchools.net
204 SusanMMa LeeSchools.net
205 BrianMM LeeSchools.Net
206 KellyAMa LeeSchools.Net
207 KarenCM LeeSchools.Net
208 ShelleyMA LeeSchools.Net
209 JoyRM leeschools.net
210 KeithBM LeeSchools.Net
211 TamikaRM LeeSchools.Net
212 EdwardAM LeeSchools.Net
213 RobertWM LeeSchools.Net
214 JeniferMM LeeSchools.net
215 CindyKM LeeSchools.Net
216 BillBM LeeSchools.Net
217 JeffFM LeeSchools.Net
218 GeorgeM LeeSchools.net
219 NancyAM LeeSchools.Net
220 BonnieMc LeeSchools.net
221 DouglasIM LeeSchools.Net
222 AngelTM LeeSchools.net
223 MatthewCM LeeSchools.Net
224 MichelleSM LeeSchools.Net
225 StephanieME LeeSchools.Net
226 ThomasJM LeeSchools.Net
227 MatthewRMi LeeSchools.Net
228 ThomasCM LeeSchools.Net
229 JenniferLMi leeschools.net
230 JenniferJM LeeSchools.net
231 SamoneLM LeeSchools.Net
232 KimberleyAMo LeeSchools.net
233 RaniceM LeeSchools.Net
234 JodyJM LeeSchools.net
235 MarcBM LeeSchools.Net
236 ObedNM LeeSchools.Net
237 JamesWMo LeeSchools.net
238 RobertVM LeeSchools.Net
239 JacksonCM LeeSchools.net
240 RobertM LeeSchools.Net
241 ElysaM LeeSchools.net
242 LisaJM LeeSchools.Net
243 ShaneEM LeeSchools.Net
244 LeilaMM LeeSchools.Net
245 AngelaMN LeeSchools.Net
246 CarlosLN LeeSchools.Net
247 OscarOl LeeSchools.net
248 LauraNO LeeSchools.Net
249 NicoleDO LeeSchools.Net
250 RosaMP LeeSchools.net
251 DouglasCP LeeSchools.Net
252 HeatherVP LeeSchools.net
253 RichardWP LeeSchools.Net
254 JasonLP LeeSchools.Net
255 SusieLP LeeSchools.net
256 JohnMP LeeSchools.Net
257 DeniseDP LeeSchools.Net
258 JosephTP LeeSchools.Net
259 MitchellDPl LeeSchools.net
260 JasonBP LeeSchools.Net
261 AmandaMP LeeSchools.net
262 BrianTP LeeSchools.net
263 AngelaJP LeeSchools.Net
264 BethanyLQ LeeSchools.net
265 SorettaER LeeSchools.net
266 VictoriaTR leeschools.net
267 VirginiaRa LeeSchools.net
268 ScottCR LeeSchools.Net
269 JamieBR LeeSchools.Net
270 MaryER LeeSchools.Net
271 KristinaLR leeschools.net
272 JosephMR LeeSchools.net
273 GingerLR LeeSchools.Net
274 EvelynRI LeeSchools.Net
275 BrianWR LeeSchools.net
276 MelissaSR LeeSchools.Net
277 MicheleRO LeeSchools.Net
278 MaryLynnR LeeSchools.net
279 AngelaRR LeeSchools.Net
280 BrianneMR LeeSchools.net
281 AndreaHR LeeSchools.net
282 JohnCRo LeeSchools.Net
283 WilliamBR LeeSchools.net
284 AidaCS LeeSchools.Net
285 DianeTS LeeSchools.Net
286 LindaJSa LeeSchools.Net
287 JeffLSa LeeSchools.Net
288 AmandaLSa LeeSchools.net
289 DavidSan LeeSchools.net
290 MarkAS LeeSchools.Net
291 DouglasOS LeeSchools.Net
292 KennethAS LeeSchools.Net
293 RebeccaJS LeeSchools.net
294 AndreaS LeeSchools.Net
295 DonnaPS LeeSchools.Net
296 RonaldKS LeeSchools.net
297 CathyMS LeeSchools.net
298 TammyMS LeeSchools.net
299 CatherineHSc leeschools.net
300 JenniferAS leeschools.net
301 JenniferJS LeeSchools.net
302 NathanS LeeSchools.net
303 ValerieES LeeSchools.Net
304 DianeMS LeeSchools.Net
305 AlS LeeSchools.Net
306 JenniferIS LeeSchools.Net
307 JamesDS LeeSchools.Net
308 ChrisMS LeeSchools.Net
309 CindyFS LeeSchools.Net
310 TinaLSil LeeSchools.net
311 JasonSSi LeeSchools.net
312 ClaytonDS LeeSchools.Net
313 PeggyLS leeschools.net
314 LisaS LeeSchools.net
315 ShannonES LeeSchools.Net
316 hollylsm LeeSchools.Net
317 JenniferMSn LeeSchools.Net
318 ReginaldHS leeschools.net
319 MarlenS LeeSchools.Net
320 KarenSSp LeeSchools.net
321 JeffSS LeeSchools.Net
322 LauraMS LeeSchools.Net
323 MicheleASt LeeSchools.net
324 LauraRSt LeeSchools.Net
325 CayceLS LeeSchools.Net
326 KellyAST LeeSchools.Net
327 MikeSt LeeSchools.net
328 SonnyAS LeeSchools.Net
329 MaggieMS LeeSchools.Net
330 AmyS LeeSchools.net
331 CrissyWS LeeSchools.Net
332 RobLS LeeSchools.Net
333 KristineASu LeeSchools.Net
334 ShellieDT LeeSchools.Net
335 KellyVT LeeSchools.net
336 MathewJT LeeSchools.net
337 MirtaST LeeSchools.Net
338 DonaldWT LeeSchools.Net
339 CheriseWT LeeSchools.Net
340 MelixsaT LeeSchools.Net
341 LauraAT LeeSchools.Net
342 WilliamBT LeeSchools.net
343 JackieBT LeeSchools.Net
344 KimberlyAV LeeSchools.Net
345 MaraKV LeeSchools.Net
346 ElizabethAVi LeeSchools.net
347 CharlesRV LeeSchools.net
348 BarbaraVO LeeSchools.Net
349 BarbaraAWa LeeSchools.Net
350 ToniMW LeeSchools.Net
351 ForrestWa LeeSchools.Net
352 KennethCW LeeSchools.Net
353 EricLW LeeSchools.Net
354 AmyEW LeeSchools.net
355 NeketaCW LeeSchools.Net
356 PadraicFW LeeSchools.net
357 RobertaAW LeeSchools.Net
358 DottieJW LeeSchools.Net
359 JenniferKW LeeSchools.net
360 HermanDW LeeSchools.Net
361 ErinRW LeeSchools.Net
362 JoeWil LeeSchools.net
363 JeremeEW LeeSchools.Net
364 EmmaMW LeeSchools.net
365 StaciaAW LeeSchools.net
366 SherriMW LeeSchools.Net
367 CarolAW LeeSchools.Net
368 KarenLWo LeeSchools.Net
369 LarryNW LeeSchools.Net
370 WinstonW LeeSchools.net
371 LisaMWr leeschools.net
372 MarlaSW LeeSchools.Net
373 RitaAZ LeeSchools.net
374 SusanKZ LeeSchools.Net
375 TraceySZ LeeSchools.Net

Binary file not shown.

View File

@ -14,10 +14,10 @@
"""Client library for using OAuth2, especially with Google APIs."""
__version__ = '1.5.1'
__version__ = '2.0.1'
GOOGLE_AUTH_URI = 'https://accounts.google.com/o/oauth2/auth'
GOOGLE_AUTH_URI = 'https://accounts.google.com/o/oauth2/v2/auth'
GOOGLE_DEVICE_URI = 'https://accounts.google.com/o/oauth2/device/code'
GOOGLE_REVOKE_URI = 'https://accounts.google.com/o/oauth2/revoke'
GOOGLE_TOKEN_URI = 'https://accounts.google.com/o/oauth2/token'
GOOGLE_TOKEN_INFO_URI = 'https://www.googleapis.com/oauth2/v2/tokeninfo'
GOOGLE_TOKEN_URI = 'https://www.googleapis.com/oauth2/v4/token'
GOOGLE_TOKEN_INFO_URI = 'https://www.googleapis.com/oauth2/v3/tokeninfo'

View File

@ -14,7 +14,7 @@
"""OpenSSL Crypto-related routines for oauth2client."""
import base64
import six
from OpenSSL import crypto
from oauth2client._helpers import _parse_pem_key
@ -68,6 +68,7 @@ class OpenSSLVerifier(object):
Raises:
OpenSSL.crypto.Error: if the key_pem can't be parsed.
"""
key_pem = _to_bytes(key_pem)
if is_x509_cert:
pubkey = crypto.load_certificate(crypto.FILETYPE_PEM, key_pem)
else:
@ -112,6 +113,7 @@ class OpenSSLSigner(object):
Raises:
OpenSSL.crypto.Error if the key can't be parsed.
"""
key = _to_bytes(key)
parsed_pem_key = _parse_pem_key(key)
if parsed_pem_key:
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, parsed_pem_key)
@ -121,19 +123,17 @@ class OpenSSLSigner(object):
return OpenSSLSigner(pkey)
def pkcs12_key_as_pem(private_key_text, private_key_password):
"""Convert the contents of a PKCS12 key to PEM using OpenSSL.
def pkcs12_key_as_pem(private_key_bytes, private_key_password):
"""Convert the contents of a PKCS#12 key to PEM using pyOpenSSL.
Args:
private_key_text: String. Private key.
private_key_password: String. Password for PKCS12.
private_key_bytes: Bytes. PKCS#12 key in DER format.
private_key_password: String. Password for PKCS#12 key.
Returns:
String. PEM contents of ``private_key_text``.
String. PEM contents of ``private_key_bytes``.
"""
decoded_body = base64.b64decode(private_key_text)
private_key_password = _to_bytes(private_key_password)
pkcs12 = crypto.load_pkcs12(decoded_body, private_key_password)
pkcs12 = crypto.load_pkcs12(private_key_bytes, private_key_password)
return crypto.dump_privatekey(crypto.FILETYPE_PEM,
pkcs12.get_privatekey())

View File

@ -0,0 +1,185 @@
# Copyright 2016 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Pure Python crypto-related routines for oauth2client.
Uses the ``rsa``, ``pyasn1`` and ``pyasn1_modules`` packages
to parse PEM files storing PKCS#1 or PKCS#8 keys as well as
certificates.
"""
from pyasn1.codec.der import decoder
from pyasn1_modules import pem
from pyasn1_modules.rfc2459 import Certificate
from pyasn1_modules.rfc5208 import PrivateKeyInfo
import rsa
import six
from oauth2client._helpers import _from_bytes
from oauth2client._helpers import _to_bytes
_PKCS12_ERROR = r"""\
PKCS12 format is not supported by the RSA library.
Either install PyOpenSSL, or please convert .p12 format
to .pem format:
$ cat key.p12 | \
> openssl pkcs12 -nodes -nocerts -passin pass:notasecret | \
> openssl rsa > key.pem
"""
_POW2 = (128, 64, 32, 16, 8, 4, 2, 1)
_PKCS1_MARKER = ('-----BEGIN RSA PRIVATE KEY-----',
'-----END RSA PRIVATE KEY-----')
_PKCS8_MARKER = ('-----BEGIN PRIVATE KEY-----',
'-----END PRIVATE KEY-----')
_PKCS8_SPEC = PrivateKeyInfo()
def _bit_list_to_bytes(bit_list):
"""Converts an iterable of 1's and 0's to bytes.
Combines the list 8 at a time, treating each group of 8 bits
as a single byte.
"""
num_bits = len(bit_list)
byte_vals = bytearray()
for start in six.moves.xrange(0, num_bits, 8):
curr_bits = bit_list[start:start + 8]
char_val = sum(val * digit
for val, digit in zip(_POW2, curr_bits))
byte_vals.append(char_val)
return bytes(byte_vals)
class RsaVerifier(object):
"""Verifies the signature on a message.
Args:
pubkey: rsa.key.PublicKey (or equiv), The public key to verify with.
"""
def __init__(self, pubkey):
self._pubkey = pubkey
def verify(self, message, signature):
"""Verifies a message against a signature.
Args:
message: string or bytes, The message to verify. If string, will be
encoded to bytes as utf-8.
signature: string or bytes, The signature on the message. If
string, will be encoded to bytes as utf-8.
Returns:
True if message was signed by the private key associated with the
public key that this object was constructed with.
"""
message = _to_bytes(message, encoding='utf-8')
try:
return rsa.pkcs1.verify(message, signature, self._pubkey)
except (ValueError, rsa.pkcs1.VerificationError):
return False
@classmethod
def from_string(cls, key_pem, is_x509_cert):
"""Construct an RsaVerifier instance from a string.
Args:
key_pem: string, public key in PEM format.
is_x509_cert: bool, True if key_pem is an X509 cert, otherwise it
is expected to be an RSA key in PEM format.
Returns:
RsaVerifier instance.
Raises:
ValueError: if the key_pem can't be parsed. In either case, error
will begin with 'No PEM start marker'. If
``is_x509_cert`` is True, will fail to find the
"-----BEGIN CERTIFICATE-----" error, otherwise fails
to find "-----BEGIN RSA PUBLIC KEY-----".
"""
key_pem = _to_bytes(key_pem)
if is_x509_cert:
der = rsa.pem.load_pem(key_pem, 'CERTIFICATE')
asn1_cert, remaining = decoder.decode(der, asn1Spec=Certificate())
if remaining != b'':
raise ValueError('Unused bytes', remaining)
cert_info = asn1_cert['tbsCertificate']['subjectPublicKeyInfo']
key_bytes = _bit_list_to_bytes(cert_info['subjectPublicKey'])
pubkey = rsa.PublicKey.load_pkcs1(key_bytes, 'DER')
else:
pubkey = rsa.PublicKey.load_pkcs1(key_pem, 'PEM')
return cls(pubkey)
class RsaSigner(object):
"""Signs messages with a private key.
Args:
pkey: rsa.key.PrivateKey (or equiv), The private key to sign with.
"""
def __init__(self, pkey):
self._key = pkey
def sign(self, message):
"""Signs a message.
Args:
message: bytes, Message to be signed.
Returns:
string, The signature of the message for the given key.
"""
message = _to_bytes(message, encoding='utf-8')
return rsa.pkcs1.sign(message, self._key, 'SHA-256')
@classmethod
def from_string(cls, key, password='notasecret'):
"""Construct an RsaSigner instance from a string.
Args:
key: string, private key in PEM format.
password: string, password for private key file. Unused for PEM
files.
Returns:
RsaSigner instance.
Raises:
ValueError if the key cannot be parsed as PKCS#1 or PKCS#8 in
PEM format.
"""
key = _from_bytes(key) # pem expects str in Py3
marker_id, key_bytes = pem.readPemBlocksFromFile(
six.StringIO(key), _PKCS1_MARKER, _PKCS8_MARKER)
if marker_id == 0:
pkey = rsa.key.PrivateKey.load_pkcs1(key_bytes,
format='DER')
elif marker_id == 1:
key_info, remaining = decoder.decode(
key_bytes, asn1Spec=_PKCS8_SPEC)
if remaining != b'':
raise ValueError('Unused bytes', remaining)
pkey_info = key_info.getComponentByName('privateKey')
pkey = rsa.key.PrivateKey.load_pkcs1(pkey_info.asOctets(),
format='DER')
else:
raise ValueError('No key could be detected.')
return cls(pkey)

View File

@ -17,7 +17,6 @@ from Crypto.PublicKey import RSA
from Crypto.Hash import SHA256
from Crypto.Signature import PKCS1_v1_5
from Crypto.Util.asn1 import DerSequence
import six
from oauth2client._helpers import _parse_pem_key
from oauth2client._helpers import _to_bytes
@ -116,14 +115,12 @@ class PyCryptoSigner(object):
Raises:
NotImplementedError if the key isn't in PEM format.
"""
parsed_pem_key = _parse_pem_key(key)
parsed_pem_key = _parse_pem_key(_to_bytes(key))
if parsed_pem_key:
pkey = RSA.importKey(parsed_pem_key)
else:
raise NotImplementedError(
'PKCS12 format is not supported by the PyCrypto library. '
'Try converting to a "PEM" '
'(openssl pkcs12 -in xxxxx.p12 -nodes -nocerts > '
'privatekey.pem) '
'or using PyOpenSSL if native code is an option.')
'No key in PEM format was detected. This implementation '
'can only use the PyCrypto library for keys in PEM '
'format.')
return PyCryptoSigner(pkey)

View File

@ -30,6 +30,7 @@ import tempfile
import time
import shutil
import six
from six.moves import http_client
from six.moves import urllib
import httplib2
@ -73,7 +74,7 @@ ID_TOKEN_VERIFICATON_CERTS = ID_TOKEN_VERIFICATION_CERTS
OOB_CALLBACK_URN = 'urn:ietf:wg:oauth:2.0:oob'
# Google Data client libraries may need to set this to [401, 403].
REFRESH_STATUS_CODES = [401]
REFRESH_STATUS_CODES = (http_client.UNAUTHORIZED,)
# The value representing user credentials.
AUTHORIZED_USER = 'authorized_user'
@ -101,6 +102,8 @@ ADC_HELP_MSG = (
'https://developers.google.com/accounts/docs/'
'application-default-credentials for more information.')
_WELL_KNOWN_CREDENTIALS_FILE = 'application_default_credentials.json'
# The access token along with the seconds in which it expires.
AccessTokenInfo = collections.namedtuple(
'AccessTokenInfo', ['access_token', 'expires_in'])
@ -115,6 +118,10 @@ _GCE_METADATA_HOST = '169.254.169.254'
_METADATA_FLAVOR_HEADER = 'Metadata-Flavor'
_DESIRED_METADATA_FLAVOR = 'Google'
# Expose utcnow() at module level to allow for
# easier testing (by replacing with a stub).
_UTCNOW = datetime.datetime.utcnow
class SETTINGS(object):
"""Settings namespace for globally defined values."""
@ -133,6 +140,13 @@ class AccessTokenRefreshError(Error):
"""Error trying to refresh an expired access token."""
class HttpAccessTokenRefreshError(AccessTokenRefreshError):
"""Error (with HTTP status) trying to refresh an expired access token."""
def __init__(self, *args, **kwargs):
super(HttpAccessTokenRefreshError, self).__init__(*args)
self.status = kwargs.get('status')
class TokenRevokeError(Error):
"""Error trying to revoke a token."""
@ -185,6 +199,13 @@ class MemoryCache(object):
self.cache.pop(key, None)
def _parse_expiry(expiry):
if expiry and isinstance(expiry, datetime.datetime):
return expiry.strftime(EXPIRY_FORMAT)
else:
return None
class Credentials(object):
"""Base class for all Credentials objects.
@ -195,7 +216,7 @@ class Credentials(object):
JSON string as input and returns an instantiated Credentials object.
"""
NON_SERIALIZED_MEMBERS = ['store']
NON_SERIALIZED_MEMBERS = frozenset(['store'])
def authorize(self, http):
"""Take an httplib2.Http instance (or equivalent) and authorizes it.
@ -236,34 +257,37 @@ class Credentials(object):
"""
_abstract()
def _to_json(self, strip):
def _to_json(self, strip, to_serialize=None):
"""Utility function that creates JSON repr. of a Credentials object.
Args:
strip: array, An array of names of members to not include in the
strip: array, An array of names of members to exclude from the
JSON.
to_serialize: dict, (Optional) The properties for this object
that will be serialized. This allows callers to modify
before serializing.
Returns:
string, a JSON representation of this instance, suitable to pass to
from_json().
"""
t = type(self)
d = copy.copy(self.__dict__)
curr_type = self.__class__
if to_serialize is None:
to_serialize = copy.copy(self.__dict__)
for member in strip:
if member in d:
del d[member]
if (d.get('token_expiry') and
isinstance(d['token_expiry'], datetime.datetime)):
d['token_expiry'] = d['token_expiry'].strftime(EXPIRY_FORMAT)
# Add in information we will need later to reconsistitue this instance.
d['_class'] = t.__name__
d['_module'] = t.__module__
for key, val in d.items():
if member in to_serialize:
del to_serialize[member]
to_serialize['token_expiry'] = _parse_expiry(
to_serialize.get('token_expiry'))
# Add in information we will need later to reconstitute this instance.
to_serialize['_class'] = curr_type.__name__
to_serialize['_module'] = curr_type.__module__
for key, val in to_serialize.items():
if isinstance(val, bytes):
d[key] = val.decode('utf-8')
to_serialize[key] = val.decode('utf-8')
if isinstance(val, set):
d[key] = list(val)
return json.dumps(d)
to_serialize[key] = list(val)
return json.dumps(to_serialize)
def to_json(self):
"""Creating a JSON representation of an instance of Credentials.
@ -272,23 +296,23 @@ class Credentials(object):
string, a JSON representation of this instance, suitable to pass to
from_json().
"""
return self._to_json(Credentials.NON_SERIALIZED_MEMBERS)
return self._to_json(self.NON_SERIALIZED_MEMBERS)
@classmethod
def new_from_json(cls, s):
def new_from_json(cls, json_data):
"""Utility class method to instantiate a Credentials subclass from JSON.
Expects the JSON string to have been produced by to_json().
Args:
s: string or bytes, JSON from to_json().
json_data: string or bytes, JSON from to_json().
Returns:
An instance of the subclass of Credentials that was serialized with
to_json().
"""
json_string_as_unicode = _from_bytes(s)
data = json.loads(json_string_as_unicode)
json_data_as_unicode = _from_bytes(json_data)
data = json.loads(json_data_as_unicode)
# Find and call the right classmethod from_json() to restore
# the object.
module_name = data['_module']
@ -303,8 +327,7 @@ class Credentials(object):
module_obj = __import__(module_name,
fromlist=module_name.split('.')[:-1])
kls = getattr(module_obj, data['_class'])
from_json = getattr(kls, 'from_json')
return from_json(json_string_as_unicode)
return kls.from_json(json_data_as_unicode)
@classmethod
def from_json(cls, unused_data):
@ -333,21 +356,31 @@ class Storage(object):
such that multiple processes and threads can operate on a single
store.
"""
def __init__(self, lock=None):
"""Create a Storage instance.
Args:
lock: An optional threading.Lock-like object. Must implement at
least acquire() and release(). Does not need to be re-entrant.
"""
self._lock = lock
def acquire_lock(self):
"""Acquires any lock necessary to access this Storage.
This lock is not reentrant.
"""
pass
if self._lock is not None:
self._lock.acquire()
def release_lock(self):
"""Release the Storage lock.
Trying to release a lock that isn't held will result in a
RuntimeError.
RuntimeError in the case of a threading.Lock or multiprocessing.Lock.
"""
pass
if self._lock is not None:
self._lock.release()
def locked_get(self):
"""Retrieve credential.
@ -676,23 +709,19 @@ class OAuth2Credentials(Credentials):
self._retrieve_scopes(http.request)
return self.scopes
def to_json(self):
return self._to_json(Credentials.NON_SERIALIZED_MEMBERS)
@classmethod
def from_json(cls, s):
def from_json(cls, json_data):
"""Instantiate a Credentials object from a JSON description of it.
The JSON should have been produced by calling .to_json() on the object.
Args:
data: dict, A deserialized JSON object.
json_data: string or bytes, JSON to deserialize.
Returns:
An instance of a Credentials subclass.
"""
s = _from_bytes(s)
data = json.loads(s)
data = json.loads(_from_bytes(json_data))
if (data.get('token_expiry') and
not isinstance(data['token_expiry'], datetime.datetime)):
try:
@ -728,7 +757,7 @@ class OAuth2Credentials(Credentials):
if not self.token_expiry:
return False
now = datetime.datetime.utcnow()
now = _UTCNOW()
if now >= self.token_expiry:
logger.info('access_token is expired. Now: %s, token_expiry: %s',
now, self.token_expiry)
@ -771,7 +800,7 @@ class OAuth2Credentials(Credentials):
valid; we just don't know anything about it.
"""
if self.token_expiry:
now = datetime.datetime.utcnow()
now = _UTCNOW()
if self.token_expiry > now:
time_delta = self.token_expiry - now
# TODO(orestica): return time_delta.total_seconds()
@ -829,7 +858,7 @@ class OAuth2Credentials(Credentials):
refresh request.
Raises:
AccessTokenRefreshError: When the refresh fails.
HttpAccessTokenRefreshError: When the refresh fails.
"""
if not self.store:
self._do_refresh_request(http_request)
@ -857,7 +886,7 @@ class OAuth2Credentials(Credentials):
refresh request.
Raises:
AccessTokenRefreshError: When the refresh fails.
HttpAccessTokenRefreshError: When the refresh fails.
"""
body = self._generate_refresh_request_body()
headers = self._generate_refresh_request_headers()
@ -866,16 +895,20 @@ class OAuth2Credentials(Credentials):
resp, content = http_request(
self.token_uri, method='POST', body=body, headers=headers)
content = _from_bytes(content)
if resp.status == 200:
if resp.status == http_client.OK:
d = json.loads(content)
self.token_response = d
self.access_token = d['access_token']
self.refresh_token = d.get('refresh_token', self.refresh_token)
if 'expires_in' in d:
self.token_expiry = datetime.timedelta(
seconds=int(d['expires_in'])) + datetime.datetime.utcnow()
delta = datetime.timedelta(seconds=int(d['expires_in']))
self.token_expiry = delta + _UTCNOW()
else:
self.token_expiry = None
if 'id_token' in d:
self.id_token = _extract_id_token(d['id_token'])
else:
self.id_token = None
# On temporary refresh errors, the user does not actually have to
# re-authorize, so we unflag here.
self.invalid = False
@ -897,7 +930,7 @@ class OAuth2Credentials(Credentials):
self.store.locked_put(self)
except (TypeError, ValueError):
pass
raise AccessTokenRefreshError(error_msg)
raise HttpAccessTokenRefreshError(error_msg, status=resp.status)
def _revoke(self, http_request):
"""Revokes this credential and deletes the stored copy (if it exists).
@ -927,7 +960,7 @@ class OAuth2Credentials(Credentials):
query_params = {'token': token}
token_revoke_uri = _update_query_params(self.revoke_uri, query_params)
resp, content = http_request(token_revoke_uri)
if resp.status == 200:
if resp.status == http_client.OK:
self.invalid = True
else:
error_msg = 'Invalid response %s.' % resp.status
@ -972,7 +1005,7 @@ class OAuth2Credentials(Credentials):
query_params)
resp, content = http_request(token_info_uri)
content = _from_bytes(content)
if resp.status == 200:
if resp.status == http_client.OK:
d = json.loads(content)
self.scopes = set(util.string_to_scopes(d.get('scope', '')))
else:
@ -1036,8 +1069,8 @@ class AccessTokenCredentials(OAuth2Credentials):
revoke_uri=revoke_uri)
@classmethod
def from_json(cls, s):
data = json.loads(_from_bytes(s))
def from_json(cls, json_data):
data = json.loads(_from_bytes(json_data))
retval = AccessTokenCredentials(
data['access_token'],
data['user_agent'])
@ -1078,7 +1111,7 @@ def _detect_gce_environment():
headers = {_METADATA_FLAVOR_HEADER: _DESIRED_METADATA_FLAVOR}
connection.request('GET', '/', headers=headers)
response = connection.getresponse()
if response.status == 200:
if response.status == http_client.OK:
return (response.getheader(_METADATA_FLAVOR_HEADER) ==
_DESIRED_METADATA_FLAVOR)
except socket.error: # socket.timeout or socket.error(64, 'Host is down')
@ -1098,7 +1131,10 @@ def _in_gae_environment():
return SETTINGS.env_name in ('GAE_PRODUCTION', 'GAE_LOCAL')
try:
import google.appengine
import google.appengine # noqa: unused import
except ImportError:
pass
else:
server_software = os.environ.get(_SERVER_SOFTWARE, '')
if server_software.startswith('Google App Engine/'):
SETTINGS.env_name = 'GAE_PRODUCTION'
@ -1106,8 +1142,6 @@ def _in_gae_environment():
elif server_software.startswith('Development/'):
SETTINGS.env_name = 'GAE_LOCAL'
return True
except ImportError:
pass
return False
@ -1152,6 +1186,11 @@ class GoogleCredentials(OAuth2Credentials):
print(response)
"""
NON_SERIALIZED_MEMBERS = (
frozenset(['_private_key']) |
OAuth2Credentials.NON_SERIALIZED_MEMBERS)
"""Members that aren't serialized when object is converted to JSON."""
def __init__(self, access_token, client_id, client_secret, refresh_token,
token_expiry, token_uri, user_agent,
revoke_uri=GOOGLE_REVOKE_URI):
@ -1194,6 +1233,32 @@ class GoogleCredentials(OAuth2Credentials):
"""
return self
@classmethod
def from_json(cls, json_data):
# TODO(issue 388): eliminate the circularity that is the reason for
# this non-top-level import.
from oauth2client.service_account import ServiceAccountCredentials
data = json.loads(_from_bytes(json_data))
# We handle service_account.ServiceAccountCredentials since it is a
# possible return type of GoogleCredentials.get_application_default()
if (data['_module'] == 'oauth2client.service_account' and
data['_class'] == 'ServiceAccountCredentials'):
return ServiceAccountCredentials.from_json(data)
token_expiry = _parse_expiry(data.get('token_expiry'))
google_credentials = cls(
data['access_token'],
data['client_id'],
data['client_secret'],
data['refresh_token'],
token_expiry,
data['token_uri'],
data['user_agent'],
revoke_uri=data.get('revoke_uri', None))
google_credentials.invalid = data['invalid']
return google_credentials
@property
def serialization_data(self):
"""Get the fields and values identifying the current credentials."""
@ -1407,9 +1472,6 @@ def _get_well_known_file():
"""Get the well known file produced by command 'gcloud auth login'."""
# TODO(orestica): Revisit this method once gcloud provides a better way
# of pinpointing the exact location of the file.
WELL_KNOWN_CREDENTIALS_FILE = 'application_default_credentials.json'
default_config_dir = os.getenv(_CLOUDSDK_CONFIG_ENV_VAR)
if default_config_dir is None:
if os.name == 'nt':
@ -1427,14 +1489,11 @@ def _get_well_known_file():
'.config',
_CLOUDSDK_CONFIG_DIRECTORY)
return os.path.join(default_config_dir, WELL_KNOWN_CREDENTIALS_FILE)
return os.path.join(default_config_dir, _WELL_KNOWN_CREDENTIALS_FILE)
def _get_application_default_credential_from_file(filename):
"""Build the Application Default Credentials from file."""
from oauth2client import service_account
# read the credentials from the file
with open(filename) as file_obj:
client_credentials = json.load(file_obj)
@ -1465,12 +1524,9 @@ def _get_application_default_credential_from_file(filename):
token_uri=GOOGLE_TOKEN_URI,
user_agent='Python client library')
else: # client_credentials['type'] == SERVICE_ACCOUNT
return service_account._ServiceAccountCredentials(
service_account_id=client_credentials['client_id'],
service_account_email=client_credentials['client_email'],
private_key_id=client_credentials['private_key_id'],
private_key_pkcs8_text=client_credentials['private_key'],
scopes=[])
from oauth2client.service_account import ServiceAccountCredentials
return ServiceAccountCredentials.from_json_keyfile_dict(
client_credentials)
def _raise_exception_for_missing_fields(missing_fields):
@ -1487,15 +1543,15 @@ def _raise_exception_for_reading_json(credential_file,
def _get_application_default_credential_GAE():
from oauth2client.appengine import AppAssertionCredentials
from oauth2client.contrib.appengine import AppAssertionCredentials
return AppAssertionCredentials([])
def _get_application_default_credential_GCE():
from oauth2client.gce import AppAssertionCredentials
from oauth2client.contrib.gce import AppAssertionCredentials
return AppAssertionCredentials([])
return AppAssertionCredentials()
class AssertionCredentials(GoogleCredentials):
@ -1561,6 +1617,18 @@ class AssertionCredentials(GoogleCredentials):
"""
self._do_revoke(http_request, self.access_token)
def sign_blob(self, blob):
"""Cryptographically sign a blob (of bytes).
Args:
blob: bytes, Message to be signed.
Returns:
tuple, A pair of the private key ID used to sign the blob and
the signed contents.
"""
raise NotImplementedError('This method is abstract.')
def _RequireCryptoOrDie():
"""Ensure we have a crypto library, or throw CryptoUnavailableError.
@ -1573,102 +1641,6 @@ def _RequireCryptoOrDie():
raise CryptoUnavailableError('No crypto library available')
class SignedJwtAssertionCredentials(AssertionCredentials):
"""Credentials object used for OAuth 2.0 Signed JWT assertion grants.
This credential does not require a flow to instantiate because it
represents a two legged flow, and therefore has all of the required
information to generate and refresh its own access tokens.
SignedJwtAssertionCredentials requires either PyOpenSSL, or PyCrypto
2.6 or later. For App Engine you may also consider using
AppAssertionCredentials.
"""
MAX_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
@util.positional(4)
def __init__(self,
service_account_name,
private_key,
scope,
private_key_password='notasecret',
user_agent=None,
token_uri=GOOGLE_TOKEN_URI,
revoke_uri=GOOGLE_REVOKE_URI,
**kwargs):
"""Constructor for SignedJwtAssertionCredentials.
Args:
service_account_name: string, id for account, usually an email
address.
private_key: string, private key in PKCS12 or PEM format.
scope: string or iterable of strings, scope(s) of the credentials
being requested.
private_key_password: string, password for private_key, unused if
private_key is in PEM format.
user_agent: string, HTTP User-Agent to provide for this
application.
token_uri: string, URI for token endpoint. For convenience defaults
to Google's endpoints but any OAuth 2.0 provider can be
used.
revoke_uri: string, URI for revoke endpoint.
kwargs: kwargs, Additional parameters to add to the JWT token, for
example sub=joe@xample.org.
Raises:
CryptoUnavailableError if no crypto library is available.
"""
_RequireCryptoOrDie()
super(SignedJwtAssertionCredentials, self).__init__(
None,
user_agent=user_agent,
token_uri=token_uri,
revoke_uri=revoke_uri,
)
self.scope = util.scopes_to_string(scope)
# Keep base64 encoded so it can be stored in JSON.
self.private_key = base64.b64encode(private_key)
self.private_key = _to_bytes(self.private_key, encoding='utf-8')
self.private_key_password = private_key_password
self.service_account_name = service_account_name
self.kwargs = kwargs
@classmethod
def from_json(cls, s):
data = json.loads(_from_bytes(s))
retval = SignedJwtAssertionCredentials(
data['service_account_name'],
base64.b64decode(data['private_key']),
data['scope'],
private_key_password=data['private_key_password'],
user_agent=data['user_agent'],
token_uri=data['token_uri'],
**data['kwargs']
)
retval.invalid = data['invalid']
retval.access_token = data['access_token']
return retval
def _generate_assertion(self):
"""Generate the assertion that will be used in the request."""
now = int(time.time())
payload = {
'aud': self.token_uri,
'scope': self.scope,
'iat': now,
'exp': now + SignedJwtAssertionCredentials.MAX_TOKEN_LIFETIME_SECS,
'iss': self.service_account_name
}
payload.update(self.kwargs)
logger.debug(str(payload))
private_key = base64.b64decode(self.private_key)
return crypt.make_signed_jwt(crypt.Signer.from_string(
private_key, self.private_key_password), payload)
# Only used in verify_id_token(), which is always calling to the same URI
# for the certs.
_cached_http = httplib2.Http(MemoryCache())
@ -1702,7 +1674,7 @@ def verify_id_token(id_token, audience, http=None,
http = _cached_http
resp, content = http.request(cert_uri)
if resp.status == 200:
if resp.status == http_client.OK:
certs = json.loads(_from_bytes(content))
return crypt.verify_signed_jwt_with_certs(id_token, certs, audience)
else:
@ -2047,7 +2019,7 @@ class OAuth2WebServerFlow(Flow):
resp, content = http.request(self.device_uri, method='POST', body=body,
headers=headers)
content = _from_bytes(content)
if resp.status == 200:
if resp.status == http_client.OK:
try:
flow_info = json.loads(content)
except ValueError as e:
@ -2130,7 +2102,7 @@ class OAuth2WebServerFlow(Flow):
resp, content = http.request(self.token_uri, method='POST', body=body,
headers=headers)
d = _parse_exchange_token_response(content)
if resp.status == 200 and 'access_token' in d:
if resp.status == http_client.OK and 'access_token' in d:
access_token = d['access_token']
refresh_token = d.get('refresh_token', None)
if not refresh_token:
@ -2139,9 +2111,8 @@ class OAuth2WebServerFlow(Flow):
"reauthenticating with approval_prompt='force'.")
token_expiry = None
if 'expires_in' in d:
token_expiry = (
datetime.datetime.utcnow() +
datetime.timedelta(seconds=int(d['expires_in'])))
delta = datetime.timedelta(seconds=int(d['expires_in']))
token_expiry = delta + _UTCNOW()
extracted_id_token = None
if 'id_token' in d:

View File

@ -121,8 +121,9 @@ def _loadfile(filename):
try:
with open(filename, 'r') as fp:
obj = json.load(fp)
except IOError:
raise InvalidClientSecretsError('File not found: "%s"' % filename)
except IOError as exc:
raise InvalidClientSecretsError('Error opening file', exc.filename,
exc.strerror, exc.errno)
return _validate_clientsecrets(obj)

View File

@ -0,0 +1,6 @@
"""Contributed modules.
Contrib contains modules that are not considered part of the core oauth2client
library but provide additional functionality. These modules are intended to
make it easier to use oauth2client.
"""

View File

@ -0,0 +1,163 @@
# Copyright 2016 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Google App Engine utilities helper.
Classes that directly require App Engine's ndb library. Provided
as a separate module in case of failure to import ndb while
other App Engine libraries are present.
"""
import logging
from google.appengine.ext import ndb
from oauth2client import client
NDB_KEY = ndb.Key
"""Key constant used by :mod:`oauth2client.contrib.appengine`."""
NDB_MODEL = ndb.Model
"""Model constant used by :mod:`oauth2client.contrib.appengine`."""
_LOGGER = logging.getLogger(__name__)
class SiteXsrfSecretKeyNDB(ndb.Model):
"""NDB Model for storage for the sites XSRF secret key.
Since this model uses the same kind as SiteXsrfSecretKey, it can be
used interchangeably. This simply provides an NDB model for interacting
with the same data the DB model interacts with.
There should only be one instance stored of this model, the one used
for the site.
"""
secret = ndb.StringProperty()
@classmethod
def _get_kind(cls):
"""Return the kind name for this class."""
return 'SiteXsrfSecretKey'
class FlowNDBProperty(ndb.PickleProperty):
"""App Engine NDB datastore Property for Flow.
Serves the same purpose as the DB FlowProperty, but for NDB models.
Since PickleProperty inherits from BlobProperty, the underlying
representation of the data in the datastore will be the same as in the
DB case.
Utility property that allows easy storage and retrieval of an
oauth2client.Flow
"""
def _validate(self, value):
"""Validates a value as a proper Flow object.
Args:
value: A value to be set on the property.
Raises:
TypeError if the value is not an instance of Flow.
"""
_LOGGER.info('validate: Got type %s', type(value))
if value is not None and not isinstance(value, client.Flow):
raise TypeError('Property %s must be convertible to a flow '
'instance; received: %s.' % (self._name,
value))
class CredentialsNDBProperty(ndb.BlobProperty):
"""App Engine NDB datastore Property for Credentials.
Serves the same purpose as the DB CredentialsProperty, but for NDB
models. Since CredentialsProperty stores data as a blob and this
inherits from BlobProperty, the data in the datastore will be the same
as in the DB case.
Utility property that allows easy storage and retrieval of Credentials
and subclasses.
"""
def _validate(self, value):
"""Validates a value as a proper credentials object.
Args:
value: A value to be set on the property.
Raises:
TypeError if the value is not an instance of Credentials.
"""
_LOGGER.info('validate: Got type %s', type(value))
if value is not None and not isinstance(value, client.Credentials):
raise TypeError('Property %s must be convertible to a '
'credentials instance; received: %s.' %
(self._name, value))
def _to_base_type(self, value):
"""Converts our validated value to a JSON serialized string.
Args:
value: A value to be set in the datastore.
Returns:
A JSON serialized version of the credential, else '' if value
is None.
"""
if value is None:
return ''
else:
return value.to_json()
def _from_base_type(self, value):
"""Converts our stored JSON string back to the desired type.
Args:
value: A value from the datastore to be converted to the
desired type.
Returns:
A deserialized Credentials (or subclass) object, else None if
the value can't be parsed.
"""
if not value:
return None
try:
# Uses the from_json method of the implied class of value
credentials = client.Credentials.new_from_json(value)
except ValueError:
credentials = None
return credentials
class CredentialsNDBModel(ndb.Model):
"""NDB Model for storage of OAuth 2.0 Credentials
Since this model uses the same kind as CredentialsModel and has a
property which can serialize and deserialize Credentials correctly, it
can be used interchangeably with a CredentialsModel to access, insert
and delete the same entities. This simply provides an NDB model for
interacting with the same data the DB model interacts with.
Storage of the model is keyed by the user.user_id().
"""
credentials = CredentialsNDBProperty()
@classmethod
def _get_kind(cls):
"""Return the kind name for this class."""
return 'CredentialsModel'

View File

@ -32,26 +32,25 @@ from google.appengine.api import memcache
from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.ext.webapp.util import login_required
from google.appengine.ext.webapp.util import run_wsgi_app
from oauth2client import GOOGLE_AUTH_URI
from oauth2client import GOOGLE_REVOKE_URI
from oauth2client import GOOGLE_TOKEN_URI
from oauth2client import clientsecrets
from oauth2client import util
from oauth2client import xsrfutil
from oauth2client.client import AccessTokenRefreshError
from oauth2client.client import AssertionCredentials
from oauth2client.client import Credentials
from oauth2client.client import Flow
from oauth2client.client import OAuth2WebServerFlow
from oauth2client.client import Storage
from oauth2client.contrib import xsrfutil
# TODO(dhermes): Resolve import issue.
# This is a temporary fix for a Google internal issue.
try:
from google.appengine.ext import ndb
except ImportError:
ndb = None
from oauth2client.contrib import _appengine_ndb
except ImportError: # pragma: NO COVER
_appengine_ndb = None
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
@ -62,6 +61,21 @@ OAUTH2CLIENT_NAMESPACE = 'oauth2client#ns'
XSRF_MEMCACHE_ID = 'xsrf_secret_key'
if _appengine_ndb is None:
CredentialsNDBModel = None
CredentialsNDBProperty = None
FlowNDBProperty = None
_NDB_KEY = None
_NDB_MODEL = None
SiteXsrfSecretKeyNDB = None
else:
CredentialsNDBModel = _appengine_ndb.CredentialsNDBModel
CredentialsNDBProperty = _appengine_ndb.CredentialsNDBProperty
FlowNDBProperty = _appengine_ndb.FlowNDBProperty
_NDB_KEY = _appengine_ndb.NDB_KEY
_NDB_MODEL = _appengine_ndb.NDB_MODEL
SiteXsrfSecretKeyNDB = _appengine_ndb.SiteXsrfSecretKeyNDB
def _safe_html(s):
"""Escape text to make it safe to display.
@ -91,24 +105,6 @@ class SiteXsrfSecretKey(db.Model):
"""
secret = db.StringProperty()
if ndb is not None:
class SiteXsrfSecretKeyNDB(ndb.Model):
"""NDB Model for storage for the sites XSRF secret key.
Since this model uses the same kind as SiteXsrfSecretKey, it can be
used interchangeably. This simply provides an NDB model for interacting
with the same data the DB model interacts with.
There should only be one instance stored of this model, the one used
for the site.
"""
secret = ndb.StringProperty()
@classmethod
def _get_kind(cls):
"""Return the kind name for this class."""
return 'SiteXsrfSecretKey'
def _generate_new_xsrf_secret_key():
"""Returns a random XSRF secret key."""
@ -166,6 +162,7 @@ class AppAssertionCredentials(AssertionCredentials):
self.scope = util.scopes_to_string(scope)
self._kwargs = kwargs
self.service_account_id = kwargs.get('service_account_id', None)
self._service_account_email = None
# Assertion type is no longer used, but still in the
# parent class signature.
@ -210,6 +207,34 @@ class AppAssertionCredentials(AssertionCredentials):
def create_scoped(self, scopes):
return AppAssertionCredentials(scopes, **self._kwargs)
def sign_blob(self, blob):
"""Cryptographically sign a blob (of bytes).
Implements abstract method
:meth:`oauth2client.client.AssertionCredentials.sign_blob`.
Args:
blob: bytes, Message to be signed.
Returns:
tuple, A pair of the private key ID used to sign the blob and
the signed contents.
"""
return app_identity.sign_blob(blob)
@property
def service_account_email(self):
"""Get the email for the current service account.
Returns:
string, The email associated with the Google App Engine
service account.
"""
if self._service_account_email is None:
self._service_account_email = (
app_identity.get_service_account_name())
return self._service_account_email
class FlowProperty(db.Property):
"""App Engine datastore Property for Flow.
@ -244,35 +269,6 @@ class FlowProperty(db.Property):
return not value
if ndb is not None:
class FlowNDBProperty(ndb.PickleProperty):
"""App Engine NDB datastore Property for Flow.
Serves the same purpose as the DB FlowProperty, but for NDB models.
Since PickleProperty inherits from BlobProperty, the underlying
representation of the data in the datastore will be the same as in the
DB case.
Utility property that allows easy storage and retrieval of an
oauth2client.Flow
"""
def _validate(self, value):
"""Validates a value as a proper Flow object.
Args:
value: A value to be set on the property.
Raises:
TypeError if the value is not an instance of Flow.
"""
logger.info('validate: Got type %s', type(value))
if value is not None and not isinstance(value, Flow):
raise TypeError('Property %s must be convertible to a flow '
'instance; received: %s.' % (self._name,
value))
class CredentialsProperty(db.Property):
"""App Engine datastore Property for Credentials.
@ -317,73 +313,6 @@ class CredentialsProperty(db.Property):
return value
if ndb is not None:
# TODO(dhermes): Turn this into a JsonProperty and overhaul the Credentials
# and subclass mechanics to use new_from_dict, to_dict,
# from_dict, etc.
class CredentialsNDBProperty(ndb.BlobProperty):
"""App Engine NDB datastore Property for Credentials.
Serves the same purpose as the DB CredentialsProperty, but for NDB
models. Since CredentialsProperty stores data as a blob and this
inherits from BlobProperty, the data in the datastore will be the same
as in the DB case.
Utility property that allows easy storage and retrieval of Credentials
and subclasses.
"""
def _validate(self, value):
"""Validates a value as a proper credentials object.
Args:
value: A value to be set on the property.
Raises:
TypeError if the value is not an instance of Credentials.
"""
logger.info('validate: Got type %s', type(value))
if value is not None and not isinstance(value, Credentials):
raise TypeError('Property %s must be convertible to a '
'credentials instance; received: %s.' %
(self._name, value))
def _to_base_type(self, value):
"""Converts our validated value to a JSON serialized string.
Args:
value: A value to be set in the datastore.
Returns:
A JSON serialized version of the credential, else '' if value
is None.
"""
if value is None:
return ''
else:
return value.to_json()
def _from_base_type(self, value):
"""Converts our stored JSON string back to the desired type.
Args:
value: A value from the datastore to be converted to the
desired type.
Returns:
A deserialized Credentials (or subclass) object, else None if
the value can't be parsed.
"""
if not value:
return None
try:
# Uses the from_json method of the implied class of value
credentials = Credentials.new_from_json(value)
except ValueError:
credentials = None
return credentials
class StorageByKeyName(Storage):
"""Store and retrieve a credential to and from the App Engine datastore.
@ -408,6 +337,8 @@ class StorageByKeyName(Storage):
user: users.User object, optional. Can be used to grab user ID as a
key_name if no key name is specified.
"""
super(StorageByKeyName, self).__init__()
if key_name is None:
if user is None:
raise ValueError('StorageByKeyName called with no '
@ -429,7 +360,7 @@ class StorageByKeyName(Storage):
# need worry about new-style classes since ndb and db models are
# new-style
if isinstance(self._model, type):
if ndb is not None and issubclass(self._model, ndb.Model):
if _NDB_MODEL is not None and issubclass(self._model, _NDB_MODEL):
return True
elif issubclass(self._model, db.Model):
return False
@ -458,7 +389,7 @@ class StorageByKeyName(Storage):
not the given key is in the datastore.
"""
if self._is_ndb():
ndb.Key(self._model, self._key_name).delete()
_NDB_KEY(self._model, self._key_name).delete()
else:
entity_key = db.Key.from_path(self._model.kind(), self._key_name)
db.delete(entity_key)
@ -517,26 +448,6 @@ class CredentialsModel(db.Model):
credentials = CredentialsProperty()
if ndb is not None:
class CredentialsNDBModel(ndb.Model):
"""NDB Model for storage of OAuth 2.0 Credentials
Since this model uses the same kind as CredentialsModel and has a
property which can serialize and deserialize Credentials correctly, it
can be used interchangeably with a CredentialsModel to access, insert
and delete the same entities. This simply provides an NDB model for
interacting with the same data the DB model interacts with.
Storage of the model is keyed by the user.user_id().
"""
credentials = CredentialsNDBProperty()
@classmethod
def _get_kind(cls):
"""Return the kind name for this class."""
return 'CredentialsModel'
def _build_state_value(request_handler, user):
"""Composes the value for the 'state' parameter.

View File

@ -14,6 +14,7 @@
"""OAuth 2.0 utitilies for Google Developer Shell environment."""
import datetime
import json
import os
import socket
@ -21,6 +22,10 @@ import socket
from oauth2client._helpers import _to_bytes
from oauth2client import client
# Expose utcnow() at module level to allow for
# easier testing (by replacing with a stub).
_UTCNOW = datetime.datetime.utcnow
DEVSHELL_ENV = 'DEVSHELL_CLIENT_PORT'
@ -52,6 +57,7 @@ class CredentialInfoResponse(object):
* Index 0 - user email
* Index 1 - default project ID. None if the project context is not known.
* Index 2 - OAuth2 access token. None if there is no valid auth context.
* Index 3 - Seconds until the access token expires. None if not present.
"""
def __init__(self, json_string):
@ -63,6 +69,7 @@ class CredentialInfoResponse(object):
self.user_email = pbl[0] if pbl_len > 0 else None
self.project_id = pbl[1] if pbl_len > 1 else None
self.access_token = pbl[2] if pbl_len > 2 else None
self.expires_in = pbl[3] if pbl_len > 3 else None
def _SendRecv():
@ -117,6 +124,12 @@ class DevshellCredentials(client.GoogleCredentials):
def _refresh(self, http_request):
self.devshell_response = _SendRecv()
self.access_token = self.devshell_response.access_token
expires_in = self.devshell_response.expires_in
if expires_in is not None:
delta = datetime.timedelta(seconds=expires_in)
self.token_expiry = _UTCNOW() + delta
else:
self.token_expiry = None
@property
def user_email(self):

View File

@ -0,0 +1,66 @@
# Copyright 2016 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Dictionary storage for OAuth2 Credentials."""
from oauth2client.client import OAuth2Credentials
from oauth2client.client import Storage
class DictionaryStorage(Storage):
"""Store and retrieve credentials to and from a dictionary-like object.
Args:
dictionary: A dictionary or dictionary-like object.
key: A string or other hashable. The credentials will be stored in
``dictionary[key]``.
lock: An optional threading.Lock-like object. The lock will be
acquired before anything is written or read from the
dictionary.
"""
def __init__(self, dictionary, key, lock=None):
"""Construct a DictionaryStorage instance."""
super(DictionaryStorage, self).__init__(lock=lock)
self._dictionary = dictionary
self._key = key
def locked_get(self):
"""Retrieve the credentials from the dictionary, if they exist.
Returns: A :class:`oauth2client.client.OAuth2Credentials` instance.
"""
serialized = self._dictionary.get(self._key)
if serialized is None:
return None
credentials = OAuth2Credentials.from_json(serialized)
credentials.set_store(self)
return credentials
def locked_put(self, credentials):
"""Save the credentials to the dictionary.
Args:
credentials: A :class:`oauth2client.client.OAuth2Credentials`
instance.
"""
serialized = credentials.to_json()
self._dictionary[self._key] = serialized
def locked_delete(self):
"""Remove the credentials from the dictionary, if they exist."""
self._dictionary.pop(self._key, None)

View File

@ -21,17 +21,17 @@ the Django datastore.
import oauth2client
import base64
import pickle
import six
from django.db import models
from django.utils.encoding import smart_bytes, smart_text
from oauth2client.client import Storage as BaseStorage
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
class CredentialsField(models.Field):
__metaclass__ = models.SubfieldBase
class CredentialsField(six.with_metaclass(models.SubfieldBase, models.Field)):
def __init__(self, *args, **kwargs):
if 'null' not in kwargs:
@ -39,24 +39,36 @@ class CredentialsField(models.Field):
super(CredentialsField, self).__init__(*args, **kwargs)
def get_internal_type(self):
return "TextField"
return 'TextField'
def to_python(self, value):
if value is None:
return None
if isinstance(value, oauth2client.client.Credentials):
return value
return pickle.loads(base64.b64decode(value))
return pickle.loads(base64.b64decode(smart_bytes(value)))
def get_db_prep_value(self, value, connection, prepared=False):
def get_prep_value(self, value):
if value is None:
return None
return base64.b64encode(pickle.dumps(value))
return smart_text(base64.b64encode(pickle.dumps(value)))
def value_to_string(self, obj):
"""Convert the field value from the provided model to a string.
Used during model serialization.
Args:
obj: db.Model, model object
Returns:
string, the serialized field value
"""
value = self._get_val_from_obj(obj)
return self.get_prep_value(value)
class FlowField(models.Field):
__metaclass__ = models.SubfieldBase
class FlowField(six.with_metaclass(models.SubfieldBase, models.Field)):
def __init__(self, *args, **kwargs):
if 'null' not in kwargs:
@ -64,7 +76,7 @@ class FlowField(models.Field):
super(FlowField, self).__init__(*args, **kwargs)
def get_internal_type(self):
return "TextField"
return 'TextField'
def to_python(self, value):
if value is None:
@ -73,14 +85,28 @@ class FlowField(models.Field):
return value
return pickle.loads(base64.b64decode(value))
def get_db_prep_value(self, value, connection, prepared=False):
def get_prep_value(self, value):
if value is None:
return None
return base64.b64encode(pickle.dumps(value))
return smart_text(base64.b64encode(pickle.dumps(value)))
def value_to_string(self, obj):
"""Convert the field value from the provided model to a string.
Used during model serialization.
Args:
obj: db.Model, model object
Returns:
string, the serialized field value
"""
value = self._get_val_from_obj(obj)
return self.get_prep_value(value)
class Storage(BaseStorage):
"""Store and retrieve a single credential to and from the datastore.
"""Store and retrieve a single credential to and from the Django datastore.
This Storage helper presumes the Credentials
have been stored as a CredenialsField
@ -98,13 +124,14 @@ class Storage(BaseStorage):
property_name: string, name of the property that is an
CredentialsProperty
"""
super(Storage, self).__init__()
self.model_class = model_class
self.key_name = key_name
self.key_value = key_value
self.property_name = property_name
def locked_get(self):
"""Retrieve Credential from datastore.
"""Retrieve stored credential.
Returns:
oauth2client.Credentials
@ -120,7 +147,7 @@ class Storage(BaseStorage):
return credential
def locked_put(self, credentials, overwrite=False):
"""Write a Credentials to the datastore.
"""Write a Credentials to the Django datastore.
Args:
credentials: Credentials, the credentials to store.

View File

@ -0,0 +1,306 @@
# Copyright 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utilities for the Django web framework
Provides Django views and helpers the make using the OAuth2 web server
flow easier. It includes an ``oauth_required`` decorator to automatically ensure
that user credentials are available, and an ``oauth_enabled`` decorator to check
if the user has authorized, and helper shortcuts to create the authorization
URL otherwise.
Configuration
=============
To configure, you'll need a set of OAuth2 web application credentials from
`Google Developer's Console <https://console.developers.google.com/project/_/apiui/credential>`.
Add the helper to your INSTALLED_APPS:
.. code-block:: python
:caption: settings.py
:name: installed_apps
INSTALLED_APPS = (
# other apps
"oauth2client.contrib.django_util"
)
Add the client secrets created earlier to the settings. You can either
specify the path to the credentials file in JSON format
.. code-block:: python
:caption: settings.py
:name: secrets_file
GOOGLE_OAUTH2_CLIENT_SECRETS_JSON=/path/to/client-secret.json
Or, directly configure the client Id and client secret.
.. code-block:: python
:caption: settings.py
:name: secrets_config
GOOGLE_OAUTH2_CLIENT_ID=client-id-field
GOOGLE_OAUTH2_CLIENT_SECRET=client-secret-field
By default, the default scopes for the required decorator only contains the
``email`` scopes. You can change that default in the settings.
.. code-block:: python
:caption: settings.py
:name: scopes
GOOGLE_OAUTH2_SCOPES = ('email', 'https://www.googleapis.com/auth/calendar',)
By default, the decorators will add an `oauth` object to the Django request
object, and include all of its state and helpers inside that object. If the
`oauth` name conflicts with another usage, it can be changed
.. code-block:: python
:caption: settings.py
:name: request_prefix
# changes request.oauth to request.google_oauth
GOOGLE_OAUTH2_REQUEST_ATTRIBUTE = 'google_oauth'
Add the oauth2 routes to your application's urls.py urlpatterns.
.. code-block:: python
:caption: urls.py
:name: urls
from oauth2client.contrib.django_util.site import urls as oauth2_urls
urlpatterns += [url(r'^oauth2/', include(oauth2_urls))]
To require OAuth2 credentials for a view, use the `oauth2_required` decorator.
This creates a credentials object with an id_token, and allows you to create an
`http` object to build service clients with. These are all attached to the
request.oauth
.. code-block:: python
:caption: views.py
:name: views_required
from oauth2client.contrib.django_util.decorators import oauth_required
@oauth_required
def requires_default_scopes(request):
email = request.oauth.credentials.id_token['email']
service = build(serviceName='calendar', version='v3',
http=request.oauth.http,
developerKey=API_KEY)
events = service.events().list(calendarId='primary').execute()['items']
return HttpResponse("email: %s , calendar: %s" % (email, str(events)))
To make OAuth2 optional and provide an authorization link in your own views.
.. code-block:: python
:caption: views.py
:name: views_enabled2
from oauth2client.contrib.django_util.decorators import oauth_enabled
@oauth_enabled
def optional_oauth2(request):
if request.oauth.has_credentials():
# this could be passed into a view
# request.oauth.http is also initialized
return HttpResponse("User email: %s"
% request.oauth.credentials.id_token['email'])
else:
return HttpResponse('Here is an OAuth Authorize link:
<a href="%s">Authorize</a>' % request.oauth.get_authorize_redirect())
If a view needs a scope not included in the default scopes specified in
the settings, you can use [incremental auth](https://developers.google.com/identity/sign-in/web/incremental-auth)
and specify additional scopes in the decorator arguments.
.. code-block:: python
:caption: views.py
:name: views_required_additional_scopes
@oauth_enabled(scopes=['https://www.googleapis.com/auth/drive'])
def drive_required(request):
if request.oauth.has_credentials():
service = build(serviceName='drive', version='v2',
http=request.oauth.http,
developerKey=API_KEY)
events = service.files().list().execute()['items']
return HttpResponse(str(events))
else:
return HttpResponse('Here is an OAuth Authorize link:
<a href="%s">Authorize</a>' % request.oauth.get_authorize_redirect())
To provide a callback on authorization being completed, use the
oauth2_authorized signal:
.. code-block:: python
:caption: views.py
:name: signals
from oauth2client.contrib.django_util.signals import oauth2_authorized
def test_callback(sender, request, credentials, **kwargs):
print "Authorization Signal Received %s" % credentials.id_token['email']
oauth2_authorized.connect(test_callback)
"""
import django.conf
from django.core import exceptions
from django.core import urlresolvers
import httplib2
from oauth2client import clientsecrets
from oauth2client.contrib.django_util import storage
from six.moves.urllib import parse
GOOGLE_OAUTH2_DEFAULT_SCOPES = ('email',)
GOOGLE_OAUTH2_REQUEST_ATTRIBUTE = 'oauth'
def _load_client_secrets(filename):
"""Loads client secrets from the given filename."""
client_type, client_info = clientsecrets.loadfile(filename)
if client_type != clientsecrets.TYPE_WEB:
raise ValueError(
'The flow specified in {} is not supported, only the WEB flow '
'type is supported.'.format(client_type))
return client_info['client_id'], client_info['client_secret']
def _get_oauth2_client_id_and_secret(settings_instance):
"""Initializes client id and client secret based on the settings"""
secret_json = getattr(django.conf.settings,
'GOOGLE_OAUTH2_CLIENT_SECRETS_JSON', None)
if secret_json is not None:
return _load_client_secrets(secret_json)
else:
client_id = getattr(settings_instance, "GOOGLE_OAUTH2_CLIENT_ID",
None)
client_secret = getattr(settings_instance,
"GOOGLE_OAUTH2_CLIENT_SECRET", None)
if client_id is not None and client_secret is not None:
return client_id, client_secret
else:
raise exceptions.ImproperlyConfigured(
"Must specify either GOOGLE_OAUTH2_CLIENT_SECRETS_JSON, or "
" both GOOGLE_OAUTH2_CLIENT_ID and GOOGLE_OAUTH2_CLIENT_SECRET "
"in settings.py")
class OAuth2Settings(object):
"""Initializes Django OAuth2 Helper Settings
This class loads the OAuth2 Settings from the Django settings, and then
provides those settings as attributes to the rest of the views and
decorators in the module.
Attributes:
scopes: A list of OAuth2 scopes that the decorators and views will use
as defaults
request_prefix: The name of the attribute that the decorators use to
attach the UserOAuth2 object to the Django request object.
client_id: The OAuth2 Client ID
client_secret: The OAuth2 Client Secret
"""
def __init__(self, settings_instance):
self.scopes = getattr(settings_instance, 'GOOGLE_OAUTH2_SCOPES',
GOOGLE_OAUTH2_DEFAULT_SCOPES)
self.request_prefix = getattr(settings_instance,
'GOOGLE_OAUTH2_REQUEST_ATTRIBUTE',
GOOGLE_OAUTH2_REQUEST_ATTRIBUTE)
self.client_id, self.client_secret = \
_get_oauth2_client_id_and_secret(settings_instance)
if ('django.contrib.sessions.middleware.SessionMiddleware'
not in settings_instance.MIDDLEWARE_CLASSES):
raise exceptions.ImproperlyConfigured(
"The Google OAuth2 Helper requires session middleware to "
"be installed. Edit your MIDDLEWARE_CLASSES setting"
" to include 'django.contrib.sessions.middleware."
"SessionMiddleware'.")
oauth2_settings = OAuth2Settings(django.conf.settings)
def _redirect_with_params(url_name, *args, **kwargs):
"""Helper method to create a redirect response that uses GET URL
parameters."""
url = urlresolvers.reverse(url_name, args=args)
params = parse.urlencode(kwargs, True)
return "{0}?{1}".format(url, params)
class UserOAuth2(object):
"""Class to create oauth2 objects on Django request objects containing
credentials and helper methods.
"""
def __init__(self, request, scopes=None, return_url=None):
"""Initialize the Oauth2 Object
:param request: Django request object
:param scopes: Scopes desired for this OAuth2 flow
:param return_url: URL to return to after authorization is complete
:return:
"""
self.request = request
self.return_url = return_url or request.get_full_path()
self.scopes = set(oauth2_settings.scopes)
if scopes:
self.scopes |= set(scopes)
# make sure previously requested custom scopes are maintained
# in future authorizations
credentials = storage.get_storage(self.request).get()
if credentials:
self.scopes |= credentials.scopes
def get_authorize_redirect(self):
"""Creates a URl to start the OAuth2 authorization flow"""
get_params = {
'return_url': self.return_url,
'scopes': self.scopes
}
return _redirect_with_params('google_oauth:authorize',
**get_params)
def has_credentials(self):
"""Returns True if there are valid credentials for the current user
and required scopes."""
return (self.credentials and not self.credentials.invalid
and self.credentials.has_scopes(self.scopes))
@property
def credentials(self):
"""Gets the authorized credentials for this flow, if they exist"""
return storage.get_storage(self.request).get()
@property
def http(self):
"""Helper method to create an HTTP client authorized with OAuth2
credentials"""
if self.has_credentials():
return self.credentials.authorize(httplib2.Http())
return None

View File

@ -0,0 +1,32 @@
# Copyright 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Application Config For Django OAuth2 Helper
Django 1.7+ provides an
[applications](https://docs.djangoproject.com/en/1.8/ref/applications/)
API so that Django projects can introspect on installed applications using a
stable API. This module exists to follow that convention.
"""
import sys
# Django 1.7+ only supports Python 2.7+
if sys.hexversion >= 0x02070000: # pragma: NO COVER
from django.apps import AppConfig
class GoogleOAuth2HelperConfig(AppConfig):
""" App Config for Django Helper"""
name = 'oauth2client.django_util'
verbose_name = "Google OAuth2 Django Helper"

View File

@ -0,0 +1,117 @@
# Copyright 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django import shortcuts
from oauth2client.contrib import django_util
from six import wraps
def oauth_required(decorated_function=None, scopes=None, **decorator_kwargs):
""" Decorator to require OAuth2 credentials for a view
.. code-block:: python
:caption: views.py
:name: views_required_2
from oauth2client.django_util.decorators import oauth_required
@oauth_required
def requires_default_scopes(request):
email = request.credentials.id_token['email']
service = build(serviceName='calendar', version='v3',
http=request.oauth.http,
developerKey=API_KEY)
events = service.events().list(
calendarId='primary').execute()['items']
return HttpResponse("email: %s , calendar: %s" % (email, str(events)))
:param decorated_function: View function to decorate, must have the Django
request object as the first argument
:param scopes: Scopes to require, will default
:param decorator_kwargs: Can include ``return_url`` to specify the URL to
return to after OAuth2 authorization is complete
:return: An OAuth2 Authorize view if credentials are not found or if the
credentials are missing the required scopes. Otherwise,
the decorated view.
"""
def curry_wrapper(wrapped_function):
@wraps(wrapped_function)
def required_wrapper(request, *args, **kwargs):
return_url = decorator_kwargs.pop('return_url',
request.get_full_path())
user_oauth = django_util.UserOAuth2(request, scopes, return_url)
if not user_oauth.has_credentials():
return shortcuts.redirect(user_oauth.get_authorize_redirect())
setattr(request, django_util.oauth2_settings.request_prefix,
user_oauth)
return wrapped_function(request, *args, **kwargs)
return required_wrapper
if decorated_function:
return curry_wrapper(decorated_function)
else:
return curry_wrapper
def oauth_enabled(decorated_function=None, scopes=None, **decorator_kwargs):
""" Decorator to enable OAuth Credentials if authorized, and setup
the oauth object on the request object to provide helper functions
to start the flow otherwise.
.. code-block:: python
:caption: views.py
:name: views_enabled3
from oauth2client.django_util.decorators import oauth_enabled
@oauth_enabled
def optional_oauth2(request):
if request.oauth.has_credentials():
# this could be passed into a view
# request.oauth.http is also initialized
return HttpResponse("User email: %s" %
request.oauth.credentials.id_token['email'])
else:
return HttpResponse('Here is an OAuth Authorize link:
<a href="%s">Authorize</a>' %
request.oauth.get_authorize_redirect())
:param decorated_function: View function to decorate
:param scopes: Scopes to require, will default
:param decorator_kwargs: Can include ``return_url`` to specify the URL to
return to after OAuth2 authorization is complete
:return: The decorated view function
"""
def curry_wrapper(wrapped_function):
@wraps(wrapped_function)
def enabled_wrapper(request, *args, **kwargs):
return_url = decorator_kwargs.pop('return_url',
request.get_full_path())
user_oauth = django_util.UserOAuth2(request, scopes, return_url)
setattr(request, django_util.oauth2_settings.request_prefix,
user_oauth)
return wrapped_function(request, *args, **kwargs)
return enabled_wrapper
if decorated_function:
return curry_wrapper(decorated_function)
else:
return curry_wrapper

View File

@ -0,0 +1,28 @@
# Copyright 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
""" Signals for Google OAuth2 Helper
This module contains signals for Google OAuth2 Helper. Currently it only
contains one, which fires when an OAuth2 authorization flow has completed.
"""
import django.dispatch
"""Signal that fires when OAuth2 Flow has completed.
It passes the Django request object and the OAuth2 credentials object to the
receiver.
"""
oauth2_authorized = django.dispatch.Signal(
providing_args=["request", "credentials"])

View File

@ -0,0 +1,23 @@
# Copyright 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.conf import urls
from oauth2client.contrib.django_util import views
urlpatterns = [
urls.url(r'oauth2callback/', views.oauth2_callback, name="callback"),
urls.url(r'oauth2authorize/', views.oauth2_authorize, name="authorize")
]
urls = (urlpatterns, "google_oauth", "google_oauth")

View File

@ -0,0 +1,27 @@
# Copyright 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from oauth2client.contrib.dictionary_storage import DictionaryStorage
_CREDENTIALS_KEY = 'google_oauth2_credentials'
def get_storage(request):
# TODO(issue 319): Make this pluggable with different storage providers
# https://github.com/google/oauth2client/issues/319
""" Gets a Credentials storage object for the Django OAuth2 Helper object
:param request: Reference to the current request object
:return: A OAuth2Client Storage implementation based on sessions
"""
return DictionaryStorage(request.session, key=_CREDENTIALS_KEY)

View File

@ -0,0 +1,139 @@
# Copyright 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import hashlib
import json
import os
import pickle
from django import http
from django.core import urlresolvers
from django import shortcuts
from oauth2client import client
from oauth2client.contrib import django_util
from oauth2client.contrib.django_util import signals
from oauth2client.contrib.django_util import storage
_CSRF_KEY = 'google_oauth2_csrf_token'
_FLOW_KEY = 'google_oauth2_flow_{0}'
def _make_flow(request, scopes, return_url=None):
"""Creates a Web Server Flow"""
# Generate a CSRF token to prevent malicious requests.
csrf_token = hashlib.sha256(os.urandom(1024)).hexdigest()
request.session[_CSRF_KEY] = csrf_token
state = json.dumps({
'csrf_token': csrf_token,
'return_url': return_url,
})
flow = client.OAuth2WebServerFlow(
client_id=django_util.oauth2_settings.client_id,
client_secret=django_util.oauth2_settings.client_secret,
scope=scopes,
state=state,
redirect_uri=request.build_absolute_uri(
urlresolvers.reverse("google_oauth:callback")))
flow_key = _FLOW_KEY.format(csrf_token)
request.session[flow_key] = pickle.dumps(flow)
return flow
def _get_flow_for_token(csrf_token, request):
""" Looks up the flow in session to recover information about requested
scopes."""
flow_pickle = request.session.get(_FLOW_KEY.format(csrf_token), None)
return None if flow_pickle is None else pickle.loads(flow_pickle)
def oauth2_callback(request):
""" View that handles the user's return from OAuth2 provider.
This view verifies the CSRF state and OAuth authorization code, and on
success stores the credentials obtained in the storage provider,
and redirects to the return_url specified in the authorize view and
stored in the session.
:param request: Django request
:return: A redirect response back to the return_url
"""
if 'error' in request.GET:
reason = request.GET.get(
'error_description', request.GET.get('error', ''))
return http.HttpResponseBadRequest(
'Authorization failed %s' % reason)
try:
encoded_state = request.GET['state']
code = request.GET['code']
except KeyError:
return http.HttpResponseBadRequest(
"Request missing state or authorization code")
try:
server_csrf = request.session[_CSRF_KEY]
except KeyError:
return http.HttpResponseBadRequest("No existing session for this flow.")
try:
state = json.loads(encoded_state)
client_csrf = state['csrf_token']
return_url = state['return_url']
except (ValueError, KeyError):
return http.HttpResponseBadRequest('Invalid state parameter.')
if client_csrf != server_csrf:
return http.HttpResponseBadRequest('Invalid CSRF token.')
flow = _get_flow_for_token(client_csrf, request)
if not flow:
return http.HttpResponseBadRequest("Missing Oauth2 flow.")
try:
credentials = flow.step2_exchange(code)
except client.FlowExchangeError as exchange_error:
return http.HttpResponseBadRequest(
"An error has occurred: {0}".format(exchange_error))
storage.get_storage(request).put(credentials)
signals.oauth2_authorized.send(sender=signals.oauth2_authorized,
request=request, credentials=credentials)
return shortcuts.redirect(return_url)
def oauth2_authorize(request):
""" View to start the OAuth2 Authorization flow
This view starts the OAuth2 authorization flow. If scopes is passed in
as a GET URL parameter, it will authorize those scopes, otherwise the
default scopes specified in settings. The return_url can also be
specified as a GET parameter, otherwise the referer header will be
checked, and if that isn't found it will return to the root path.
:param request: The Django request object
:return: A redirect to Google OAuth2 Authorization
"""
scopes = request.GET.getlist('scopes', django_util.oauth2_settings.scopes)
return_url = request.GET.get('return_url', None)
if not return_url:
return_url = request.META.get('HTTP_REFERER', '/')
flow = _make_flow(request=request, scopes=scopes, return_url=return_url)
auth_url = flow.step1_get_authorize_url()
return shortcuts.redirect(auth_url)

View File

@ -23,22 +23,23 @@ available.
Configuration
=============
To configure, you'll need a set of OAuth2 client ID from the
To configure, you'll need a set of OAuth2 web application credentials from the
`Google Developer's Console <https://console.developers.google.com/project/_/\
apiui/credential>`__.
.. code-block:: python
from oauth2client.flask_util import UserOAuth2
from oauth2client.contrib.flask_util import UserOAuth2
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['OAUTH2_CLIENT_SECRETS_JSON'] = 'client_secrets.json'
app.config['GOOGLE_OAUTH2_CLIENT_SECRETS_JSON'] = 'client_secrets.json'
# or, specify the client id and secret separately
app.config['OAUTH2_CLIENT_ID'] = 'your-client-id'
app.config['OAUTH2_CLIENT_SECRET'] = 'your-client-secret'
app.config['GOOGLE_OAUTH2_CLIENT_ID'] = 'your-client-id'
app.config['GOOGLE_OAUTH2_CLIENT_SECRET'] = 'your-client-secret'
oauth2 = UserOAuth2(app)
@ -164,6 +165,7 @@ available outside of a request context, you will need to implement your own
import hashlib
import json
import os
import pickle
from functools import wraps
import six.moves.http_client as httplib
@ -181,16 +183,29 @@ except ImportError: # pragma: NO COVER
raise ImportError('The flask utilities require flask 0.9 or newer.')
from oauth2client.client import FlowExchangeError
from oauth2client.client import OAuth2Credentials
from oauth2client.client import OAuth2WebServerFlow
from oauth2client.client import Storage
from oauth2client.contrib.dictionary_storage import DictionaryStorage
from oauth2client import clientsecrets
from oauth2client import util
__author__ = 'jonwayne@google.com (Jon Wayne Parrott)'
DEFAULT_SCOPES = ('email',)
_DEFAULT_SCOPES = ('email',)
_CREDENTIALS_KEY = 'google_oauth2_credentials'
_FLOW_KEY = 'google_oauth2_flow_{0}'
_CSRF_KEY = 'google_oauth2_csrf_token'
def _get_flow_for_token(csrf_token):
"""Retrieves the flow instance associated with a given CSRF token from
the Flask session."""
flow_pickle = session.get(
_FLOW_KEY.format(csrf_token), None)
if flow_pickle is None:
return None
else:
return pickle.loads(flow_pickle)
class UserOAuth2(object):
@ -202,10 +217,11 @@ class UserOAuth2(object):
file, obtained from the credentials screen in the Google Developers
console.
* ``GOOGLE_OAUTH2_CLIENT_ID`` the oauth2 credentials' client ID. This
is only needed if ``OAUTH2_CLIENT_SECRETS_JSON`` is not specified.
* ``GOOGLE_OAUTH2_CLIENT_SECRET`` the oauth2 credentials' client
secret. This is only needed if ``OAUTH2_CLIENT_SECRETS_JSON`` is not
is only needed if ``GOOGLE_OAUTH2_CLIENT_SECRETS_JSON`` is not
specified.
* ``GOOGLE_OAUTH2_CLIENT_SECRET`` the oauth2 credentials' client
secret. This is only needed if ``GOOGLE_OAUTH2_CLIENT_SECRETS_JSON``
is not specified.
If app is specified, all arguments will be passed along to init_app.
@ -227,7 +243,8 @@ class UserOAuth2(object):
app: A Flask application.
scopes: Optional list of scopes to authorize.
client_secrets_file: Path to a file containing client secrets. You
can also specify the OAUTH2_CLIENT_SECRETS_JSON config value.
can also specify the GOOGLE_OAUTH2_CLIENT_SECRETS_JSON config
value.
client_id: If not specifying a client secrets file, specify the
OAuth2 client id. You can also specify the
GOOGLE_OAUTH2_CLIENT_ID config value. You must also provide a
@ -246,11 +263,11 @@ class UserOAuth2(object):
self.flow_kwargs = kwargs
if storage is None:
storage = FlaskSessionStorage()
storage = DictionaryStorage(session, key=_CREDENTIALS_KEY)
self.storage = storage
if scopes is None:
scopes = app.config.get('GOOGLE_OAUTH2_SCOPES', DEFAULT_SCOPES)
scopes = app.config.get('GOOGLE_OAUTH2_SCOPES', _DEFAULT_SCOPES)
self.scopes = scopes
self._load_config(client_secrets_file, client_id, client_secret)
@ -300,7 +317,8 @@ class UserOAuth2(object):
client_type, client_info = clientsecrets.loadfile(filename)
if client_type != clientsecrets.TYPE_WEB:
raise ValueError(
'The flow specified in %s is not supported.' % client_type)
'The flow specified in {0} is not supported.'.format(
client_type))
self.client_id = client_info['client_id']
self.client_secret = client_info['client_secret']
@ -310,7 +328,7 @@ class UserOAuth2(object):
# Generate a CSRF token to prevent malicious requests.
csrf_token = hashlib.sha256(os.urandom(1024)).hexdigest()
session['google_oauth2_csrf_token'] = csrf_token
session[_CSRF_KEY] = csrf_token
state = json.dumps({
'csrf_token': csrf_token,
@ -320,10 +338,10 @@ class UserOAuth2(object):
kw = self.flow_kwargs.copy()
kw.update(kwargs)
extra_scopes = util.scopes_to_string(kw.pop('scopes', ''))
scopes = ' '.join([util.scopes_to_string(self.scopes), extra_scopes])
extra_scopes = kw.pop('scopes', [])
scopes = set(self.scopes).union(set(extra_scopes))
return OAuth2WebServerFlow(
flow = OAuth2WebServerFlow(
client_id=self.client_id,
client_secret=self.client_secret,
scope=scopes,
@ -331,6 +349,11 @@ class UserOAuth2(object):
redirect_uri=url_for('oauth2.callback', _external=True),
**kw)
flow_key = _FLOW_KEY.format(csrf_token)
session[flow_key] = pickle.dumps(flow)
return flow
def _create_blueprint(self):
bp = Blueprint('oauth2', __name__)
bp.add_url_rule('/oauth2authorize', 'authorize', self.authorize_view)
@ -367,11 +390,12 @@ class UserOAuth2(object):
if 'error' in request.args:
reason = request.args.get(
'error_description', request.args.get('error', ''))
return 'Authorization failed: %s' % reason, httplib.BAD_REQUEST
return ('Authorization failed: {0}'.format(reason),
httplib.BAD_REQUEST)
try:
encoded_state = request.args['state']
server_csrf = session['google_oauth2_csrf_token']
server_csrf = session[_CSRF_KEY]
code = request.args['code']
except KeyError:
return 'Invalid request', httplib.BAD_REQUEST
@ -386,14 +410,17 @@ class UserOAuth2(object):
if client_csrf != server_csrf:
return 'Invalid request state', httplib.BAD_REQUEST
flow = self._make_flow()
flow = _get_flow_for_token(server_csrf)
if flow is None:
return 'Invalid request state', httplib.BAD_REQUEST
# Exchange the auth code for credentials.
try:
credentials = flow.step2_exchange(code)
except FlowExchangeError as exchange_error:
current_app.logger.exception(exchange_error)
content = 'An error occurred: %s' % (exchange_error,)
content = 'An error occurred: {0}'.format(exchange_error)
return content, httplib.BAD_REQUEST
# Save the credentials to the storage.
@ -409,7 +436,7 @@ class UserOAuth2(object):
"""The credentials for the current user or None if unavailable."""
ctx = _app_ctx_stack.top
if not hasattr(ctx, 'google_oauth2_credentials'):
if not hasattr(ctx, _CREDENTIALS_KEY):
ctx.google_oauth2_credentials = self.storage.get()
return ctx.google_oauth2_credentials
@ -432,7 +459,7 @@ class UserOAuth2(object):
return self.credentials.id_token['email']
except KeyError:
current_app.logger.error(
'Invalid id_token %s', self.credentials.id_token)
'Invalid id_token {0}'.format(self.credentials.id_token))
@property
def user_id(self):
@ -448,7 +475,7 @@ class UserOAuth2(object):
return self.credentials.id_token['sub']
except KeyError:
current_app.logger.error(
'Invalid id_token %s', self.credentials.id_token)
'Invalid id_token {0}'.format(self.credentials.id_token))
def authorize_url(self, return_url, **kwargs):
"""Creates a URL that can be used to start the authorization flow.
@ -473,28 +500,30 @@ class UserOAuth2(object):
def curry_wrapper(wrapped_function):
@wraps(wrapped_function)
def required_wrapper(*args, **kwargs):
return_url = decorator_kwargs.pop('return_url', request.url)
# No credentials, redirect for new authorization.
if not self.has_credentials():
requested_scopes = set(self.scopes)
if scopes is not None:
requested_scopes |= set(scopes)
if self.has_credentials():
requested_scopes |= self.credentials.scopes
requested_scopes = list(requested_scopes)
# Does the user have credentials and does the credentials have
# all of the needed scopes?
if (self.has_credentials() and
self.credentials.has_scopes(requested_scopes)):
return wrapped_function(*args, **kwargs)
# Otherwise, redirect to authorization
else:
auth_url = self.authorize_url(
return_url,
scopes=scopes,
scopes=requested_scopes,
**decorator_kwargs)
return redirect(auth_url)
# Existing credentials but mismatching scopes, redirect for
# incremental authorization.
if scopes and not self.credentials.has_scopes(scopes):
auth_url = self.authorize_url(
return_url,
scopes=list(self.credentials.scopes) + scopes,
**decorator_kwargs)
return redirect(auth_url)
return wrapped_function(*args, **kwargs)
return required_wrapper
if decorated_function:
@ -518,31 +547,3 @@ class UserOAuth2(object):
if not self.credentials:
raise ValueError('No credentials available.')
return self.credentials.authorize(httplib2.Http(*args, **kwargs))
class FlaskSessionStorage(Storage):
"""Storage implementation that uses Flask sessions.
Note that flask's default sessions are signed but not encrypted. Users
can see their own credentials and non-https connections can intercept user
credentials. We strongly recommend using a server-side session
implementation.
"""
def locked_get(self):
serialized = session.get('google_oauth2_credentials')
if serialized is None:
return None
credentials = OAuth2Credentials.from_json(serialized)
credentials.set_store(self)
return credentials
def locked_put(self, credentials):
session['google_oauth2_credentials'] = credentials.to_json()
def locked_delete(self):
if 'google_oauth2_credentials' in session:
del session['google_oauth2_credentials']

View File

@ -0,0 +1,194 @@
# Copyright 2014 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utilities for Google Compute Engine
Utilities for making it easier to use OAuth 2.0 on Google Compute Engine.
"""
import json
import logging
import warnings
import httplib2
from six.moves import http_client
from six.moves import urllib
from oauth2client._helpers import _from_bytes
from oauth2client import util
from oauth2client.client import HttpAccessTokenRefreshError
from oauth2client.client import AssertionCredentials
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
logger = logging.getLogger(__name__)
# URI Template for the endpoint that returns access_tokens.
_METADATA_ROOT = ('http://metadata.google.internal/computeMetadata/v1/'
'instance/service-accounts/default/')
META = _METADATA_ROOT + 'token'
_DEFAULT_EMAIL_METADATA = _METADATA_ROOT + 'email'
_SCOPES_WARNING = """\
You have requested explicit scopes to be used with a GCE service account.
Using this argument will have no effect on the actual scopes for tokens
requested. These scopes are set at VM instance creation time and
can't be overridden in the request.
"""
def _get_service_account_email(http_request=None):
"""Get the GCE service account email from the current environment.
Args:
http_request: callable, (Optional) a callable that matches the method
signature of httplib2.Http.request, used to make
the request to the metadata service.
Returns:
tuple, A pair where the first entry is an optional response (from a
failed request) and the second is service account email found (as
a string).
"""
if http_request is None:
http_request = httplib2.Http().request
response, content = http_request(
_DEFAULT_EMAIL_METADATA, headers={'Metadata-Flavor': 'Google'})
if response.status == http_client.OK:
content = _from_bytes(content)
return None, content
else:
return response, content
class AppAssertionCredentials(AssertionCredentials):
"""Credentials object for Compute Engine Assertion Grants
This object will allow a Compute Engine instance to identify itself to
Google and other OAuth 2.0 servers that can verify assertions. It can be
used for the purpose of accessing data stored under an account assigned to
the Compute Engine instance itself.
This credential does not require a flow to instantiate because it
represents a two legged flow, and therefore has all of the required
information to generate and refresh its own access tokens.
"""
@util.positional(2)
def __init__(self, scope='', **kwargs):
"""Constructor for AppAssertionCredentials
Args:
scope: string or iterable of strings, scope(s) of the credentials
being requested. Using this argument will have no effect on
the actual scopes for tokens requested. These scopes are
set at VM instance creation time and won't change.
"""
if scope:
warnings.warn(_SCOPES_WARNING)
# This is just provided for backwards compatibility, but is not
# used by this class.
self.scope = util.scopes_to_string(scope)
self.kwargs = kwargs
# Assertion type is no longer used, but still in the
# parent class signature.
super(AppAssertionCredentials, self).__init__(None)
self._service_account_email = None
@classmethod
def from_json(cls, json_data):
data = json.loads(_from_bytes(json_data))
return AppAssertionCredentials(data['scope'])
def _refresh(self, http_request):
"""Refreshes the access_token.
Skip all the storage hoops and just refresh using the API.
Args:
http_request: callable, a callable that matches the method
signature of httplib2.Http.request, used to make
the refresh request.
Raises:
HttpAccessTokenRefreshError: When the refresh fails.
"""
response, content = http_request(
META, headers={'Metadata-Flavor': 'Google'})
content = _from_bytes(content)
if response.status == http_client.OK:
try:
token_content = json.loads(content)
except Exception as e:
raise HttpAccessTokenRefreshError(str(e),
status=response.status)
self.access_token = token_content['access_token']
else:
if response.status == http_client.NOT_FOUND:
content += (' This can occur if a VM was created'
' with no service account or scopes.')
raise HttpAccessTokenRefreshError(content, status=response.status)
@property
def serialization_data(self):
raise NotImplementedError(
'Cannot serialize credentials for GCE service accounts.')
def create_scoped_required(self):
return False
def create_scoped(self, scopes):
return AppAssertionCredentials(scopes, **self.kwargs)
def sign_blob(self, blob):
"""Cryptographically sign a blob (of bytes).
This method is provided to support a common interface, but
the actual key used for a Google Compute Engine service account
is not available, so it can't be used to sign content.
Args:
blob: bytes, Message to be signed.
Raises:
NotImplementedError, always.
"""
raise NotImplementedError(
'Compute Engine service accounts cannot sign blobs')
@property
def service_account_email(self):
"""Get the email for the current service account.
Uses the Google Compute Engine metadata service to retrieve the email
of the default service account.
Returns:
string, The email associated with the Google Compute Engine
service account.
Raises:
AttributeError, if the email can not be retrieved from the Google
Compute Engine metadata service.
"""
if self._service_account_email is None:
failure, email = _get_service_account_email()
if failure is None:
self._service_account_email = email
else:
raise AttributeError('Failed to retrieve the email from the '
'Google Compute Engine metadata service',
failure, email)
return self._service_account_email

View File

@ -59,24 +59,9 @@ class Storage(BaseStorage):
credentials are stored.
user_name: string, The name of the user to store credentials for.
"""
super(Storage, self).__init__(lock=threading.Lock())
self._service_name = service_name
self._user_name = user_name
self._lock = threading.Lock()
def acquire_lock(self):
"""Acquires any lock necessary to access this Storage.
This lock is not reentrant.
"""
self._lock.acquire()
def release_lock(self):
"""Release the Storage lock.
Trying to release a lock that isn't held will result in a
RuntimeError.
"""
self._lock.release()
def locked_get(self):
"""Retrieve Credential from file.

View File

@ -53,7 +53,7 @@ import threading
from oauth2client.client import Credentials
from oauth2client.client import Storage as BaseStorage
from oauth2client import util
from oauth2client.locked_file import LockedFile
from oauth2client.contrib.locked_file import LockedFile
__author__ = 'jbeda@google.com (Joe Beda)'
@ -73,6 +73,21 @@ class NewerCredentialStoreError(Error):
"""The credential store is a newer version than supported."""
def _dict_to_tuple_key(dictionary):
"""Converts a dictionary to a tuple that can be used as an immutable key.
The resulting key is always sorted so that logically equivalent
dictionaries always produce an identical tuple for a key.
Args:
dictionary: the dictionary to use as the key.
Returns:
A tuple representing the dictionary in it's naturally sorted ordering.
"""
return tuple(sorted(dictionary.items()))
@util.positional(4)
def get_credential_storage(filename, client_id, user_agent, scope,
warn_on_readonly=True):
@ -139,7 +154,7 @@ def get_credential_storage_custom_key(filename, key_dict,
credential.
"""
multistore = _get_multistore(filename, warn_on_readonly=warn_on_readonly)
key = util.dict_to_tuple_key(key_dict)
key = _dict_to_tuple_key(key_dict)
return multistore._get_storage(key)
@ -290,6 +305,11 @@ class _MultiStore(object):
elif e.errno == errno.ENOLCK:
logger.warn('File system is out of resources for writing the '
'credentials file (is your disk full?).')
elif e.errno == errno.EDEADLK:
logger.warn('Lock contention on multistore file, opening '
'in read-only mode.')
elif e.errno == errno.EACCES:
logger.warn('Cannot access credentials file.')
else:
raise
if not self._file.is_locked():
@ -399,7 +419,7 @@ class _MultiStore(object):
OAuth2Credential object.
"""
raw_key = cred_entry['key']
key = util.dict_to_tuple_key(raw_key)
key = _dict_to_tuple_key(raw_key)
credential = None
credential = Credentials.new_from_json(
json.dumps(cred_entry['credential']))

View File

@ -1,4 +1,3 @@
#
# Copyright 2014 the Melange authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -18,7 +17,6 @@
import base64
import binascii
import hmac
import six
import time
from oauth2client._helpers import _to_bytes

View File

@ -24,6 +24,8 @@ from oauth2client._helpers import _json_encode
from oauth2client._helpers import _to_bytes
from oauth2client._helpers import _urlsafe_b64decode
from oauth2client._helpers import _urlsafe_b64encode
from oauth2client._pure_python_crypt import RsaSigner
from oauth2client._pure_python_crypt import RsaVerifier
CLOCK_SKEW_SECS = 300 # 5 minutes in seconds
@ -65,11 +67,11 @@ elif PyCryptoSigner: # pragma: NO COVER
Signer = PyCryptoSigner
Verifier = PyCryptoVerifier
else: # pragma: NO COVER
raise ImportError('No encryption library found. Please install either '
'PyOpenSSL, or PyCrypto 2.6 or later')
Signer = RsaSigner
Verifier = RsaVerifier
def make_signed_jwt(signer, payload):
def make_signed_jwt(signer, payload, key_id=None):
"""Make a signed JWT.
See http://self-issued.info/docs/draft-jones-json-web-token.html.
@ -77,15 +79,18 @@ def make_signed_jwt(signer, payload):
Args:
signer: crypt.Signer, Cryptographic signer.
payload: dict, Dictionary of data to convert to JSON and then sign.
key_id: string, (Optional) Key ID header.
Returns:
string, The JWT for the payload.
"""
header = {'typ': 'JWT', 'alg': 'RS256'}
if key_id is not None:
header['kid'] = key_id
segments = [
_urlsafe_b64encode(_json_encode(header)),
_urlsafe_b64encode(_json_encode(payload)),
_urlsafe_b64encode(_json_encode(header)),
_urlsafe_b64encode(_json_encode(payload)),
]
signing_input = b'.'.join(segments)

View File

@ -36,29 +36,14 @@ class Storage(BaseStorage):
"""Store and retrieve a single credential to and from a file."""
def __init__(self, filename):
super(Storage, self).__init__(lock=threading.Lock())
self._filename = filename
self._lock = threading.Lock()
def _validate_file(self):
if os.path.islink(self._filename):
raise CredentialsFileSymbolicLinkError(
'File: %s is a symbolic link.' % self._filename)
def acquire_lock(self):
"""Acquires any lock necessary to access this Storage.
This lock is not reentrant.
"""
self._lock.acquire()
def release_lock(self):
"""Release the Storage lock.
Trying to release a lock that isn't held will result in a
RuntimeError.
"""
self._lock.release()
def locked_get(self):
"""Retrieve Credential from file.

View File

@ -1,110 +0,0 @@
# Copyright 2014 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utilities for Google Compute Engine
Utilities for making it easier to use OAuth 2.0 on Google Compute Engine.
"""
import json
import logging
from six.moves import urllib
from oauth2client._helpers import _from_bytes
from oauth2client import util
from oauth2client.client import AccessTokenRefreshError
from oauth2client.client import AssertionCredentials
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
logger = logging.getLogger(__name__)
# URI Template for the endpoint that returns access_tokens.
META = ('http://metadata.google.internal/0.1/meta-data/service-accounts/'
'default/acquire{?scope}')
class AppAssertionCredentials(AssertionCredentials):
"""Credentials object for Compute Engine Assertion Grants
This object will allow a Compute Engine instance to identify itself to
Google and other OAuth 2.0 servers that can verify assertions. It can be
used for the purpose of accessing data stored under an account assigned to
the Compute Engine instance itself.
This credential does not require a flow to instantiate because it
represents a two legged flow, and therefore has all of the required
information to generate and refresh its own access tokens.
"""
@util.positional(2)
def __init__(self, scope, **kwargs):
"""Constructor for AppAssertionCredentials
Args:
scope: string or iterable of strings, scope(s) of the credentials
being requested.
"""
self.scope = util.scopes_to_string(scope)
self.kwargs = kwargs
# Assertion type is no longer used, but still in the
# parent class signature.
super(AppAssertionCredentials, self).__init__(None)
@classmethod
def from_json(cls, json_data):
data = json.loads(_from_bytes(json_data))
return AppAssertionCredentials(data['scope'])
def _refresh(self, http_request):
"""Refreshes the access_token.
Skip all the storage hoops and just refresh using the API.
Args:
http_request: callable, a callable that matches the method
signature of httplib2.Http.request, used to make
the refresh request.
Raises:
AccessTokenRefreshError: When the refresh fails.
"""
query = '?scope=%s' % urllib.parse.quote(self.scope, '')
uri = META.replace('{?scope}', query)
response, content = http_request(uri)
content = _from_bytes(content)
if response.status == 200:
try:
d = json.loads(content)
except Exception as e:
raise AccessTokenRefreshError(str(e))
self.access_token = d['accessToken']
else:
if response.status == 404:
content += (' This can occur if a VM was created'
' with no service account or scopes.')
raise AccessTokenRefreshError(content)
@property
def serialization_data(self):
raise NotImplementedError(
'Cannot serialize credentials for GCE service accounts.')
def create_scoped_required(self):
return not self.scope
def create_scoped(self, scopes):
return AppAssertionCredentials(scopes, **self.kwargs)

View File

@ -1,161 +0,0 @@
# Copyright 2014 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""This module holds the old run() function which is deprecated, the
tools.run_flow() function should be used in its place."""
from __future__ import print_function
import logging
import socket
import sys
import webbrowser
import gflags
from six.moves import input
from oauth2client import client
from oauth2client import util
from oauth2client.tools import ClientRedirectHandler
from oauth2client.tools import ClientRedirectServer
FLAGS = gflags.FLAGS
gflags.DEFINE_boolean('auth_local_webserver', True,
('Run a local web server to handle redirects during '
'OAuth authorization.'))
gflags.DEFINE_string('auth_host_name', 'localhost',
('Host name to use when running a local web server to '
'handle redirects during OAuth authorization.'))
gflags.DEFINE_multi_int('auth_host_port', [8080, 8090],
('Port to use when running a local web server to '
'handle redirects during OAuth authorization.'))
@util.positional(2)
def run(flow, storage, http=None):
"""Core code for a command-line application.
The ``run()`` function is called from your application and runs
through all the steps to obtain credentials. It takes a ``Flow``
argument and attempts to open an authorization server page in the
user's default web browser. The server asks the user to grant your
application access to the user's data. If the user grants access,
the ``run()`` function returns new credentials. The new credentials
are also stored in the ``storage`` argument, which updates the file
associated with the ``Storage`` object.
It presumes it is run from a command-line application and supports the
following flags:
``--auth_host_name`` (string, default: ``localhost``)
Host name to use when running a local web server to handle
redirects during OAuth authorization.
``--auth_host_port`` (integer, default: ``[8080, 8090]``)
Port to use when running a local web server to handle redirects
during OAuth authorization. Repeat this option to specify a list
of values.
``--[no]auth_local_webserver`` (boolean, default: ``True``)
Run a local web server to handle redirects during OAuth authorization.
Since it uses flags make sure to initialize the ``gflags`` module before
calling ``run()``.
Args:
flow: Flow, an OAuth 2.0 Flow to step through.
storage: Storage, a ``Storage`` to store the credential in.
http: An instance of ``httplib2.Http.request`` or something that acts
like it.
Returns:
Credentials, the obtained credential.
"""
logging.warning('This function, oauth2client.tools.run(), and the use of '
'the gflags library are deprecated and will be removed in a future '
'version of the library.')
if FLAGS.auth_local_webserver:
success = False
port_number = 0
for port in FLAGS.auth_host_port:
port_number = port
try:
httpd = ClientRedirectServer((FLAGS.auth_host_name, port),
ClientRedirectHandler)
except socket.error as e:
pass
else:
success = True
break
FLAGS.auth_local_webserver = success
if not success:
print('Failed to start a local webserver listening on either port 8080')
print('or port 9090. Please check your firewall settings and locally')
print('running programs that may be blocking or using those ports.')
print()
print('Falling back to --noauth_local_webserver and continuing with')
print('authorization.')
print()
if FLAGS.auth_local_webserver:
oauth_callback = 'http://%s:%s/' % (FLAGS.auth_host_name, port_number)
else:
oauth_callback = client.OOB_CALLBACK_URN
flow.redirect_uri = oauth_callback
authorize_url = flow.step1_get_authorize_url()
if FLAGS.auth_local_webserver:
webbrowser.open(authorize_url, new=1, autoraise=True)
print('Your browser has been opened to visit:')
print()
print(' ' + authorize_url)
print()
print('If your browser is on a different machine then exit and re-run')
print('this application with the command-line parameter ')
print()
print(' --noauth_local_webserver')
print()
else:
print('Go to the following link in your browser:')
print()
print(' ' + authorize_url)
print()
code = None
if FLAGS.auth_local_webserver:
httpd.handle_request()
if 'error' in httpd.query_params:
sys.exit('Authentication request was rejected.')
if 'code' in httpd.query_params:
code = httpd.query_params['code']
else:
print('Failed to find "code" in the query parameters of the redirect.')
sys.exit('Try running with --noauth_local_webserver.')
else:
code = input('Enter verification code: ').strip()
try:
credential = flow.step2_exchange(code, http=http)
except client.FlowExchangeError as e:
sys.exit('Authentication has failed: %s' % e)
storage.put(credential)
credential.set_store(storage)
print('Authentication successful.')
return credential

View File

@ -12,122 +12,452 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""A service account credentials class.
This credentials class is implemented on top of rsa library.
"""
"""oauth2client Service account credentials class."""
import base64
import copy
import datetime
import json
import time
from pyasn1.codec.ber import decoder
from pyasn1_modules.rfc5208 import PrivateKeyInfo
import rsa
from oauth2client import GOOGLE_REVOKE_URI
from oauth2client import GOOGLE_TOKEN_URI
from oauth2client._helpers import _json_encode
from oauth2client._helpers import _to_bytes
from oauth2client._helpers import _from_bytes
from oauth2client._helpers import _urlsafe_b64encode
from oauth2client import util
from oauth2client.client import AssertionCredentials
from oauth2client.client import EXPIRY_FORMAT
from oauth2client.client import SERVICE_ACCOUNT
from oauth2client import crypt
class _ServiceAccountCredentials(AssertionCredentials):
"""Class representing a service account (signed JWT) credential."""
_PASSWORD_DEFAULT = 'notasecret'
_PKCS12_KEY = '_private_key_pkcs12'
_PKCS12_ERROR = r"""
This library only implements PKCS#12 support via the pyOpenSSL library.
Either install pyOpenSSL, or please convert the .p12 file
to .pem format:
$ cat key.p12 | \
> openssl pkcs12 -nodes -nocerts -passin pass:notasecret | \
> openssl rsa > key.pem
"""
MAX_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
def __init__(self, service_account_id, service_account_email,
private_key_id, private_key_pkcs8_text, scopes,
user_agent=None, token_uri=GOOGLE_TOKEN_URI,
revoke_uri=GOOGLE_REVOKE_URI, **kwargs):
class ServiceAccountCredentials(AssertionCredentials):
"""Service Account credential for OAuth 2.0 signed JWT grants.
super(_ServiceAccountCredentials, self).__init__(
None, user_agent=user_agent, token_uri=token_uri,
revoke_uri=revoke_uri)
Supports
* JSON keyfile (typically contains a PKCS8 key stored as
PEM text)
* ``.p12`` key (stores PKCS12 key and certificate)
Makes an assertion to server using a signed JWT assertion in exchange
for an access token.
This credential does not require a flow to instantiate because it
represents a two legged flow, and therefore has all of the required
information to generate and refresh its own access tokens.
Args:
service_account_email: string, The email associated with the
service account.
signer: ``crypt.Signer``, A signer which can be used to sign content.
scopes: List or string, (Optional) Scopes to use when acquiring
an access token.
private_key_id: string, (Optional) Private key identifier. Typically
only used with a JSON keyfile. Can be sent in the
header of a JWT token assertion.
client_id: string, (Optional) Client ID for the project that owns the
service account.
user_agent: string, (Optional) User agent to use when sending
request.
kwargs: dict, Extra key-value pairs (both strings) to send in the
payload body when making an assertion.
"""
MAX_TOKEN_LIFETIME_SECS = 3600
"""Max lifetime of the token (one hour, in seconds)."""
NON_SERIALIZED_MEMBERS = (
frozenset(['_signer']) |
AssertionCredentials.NON_SERIALIZED_MEMBERS)
"""Members that aren't serialized when object is converted to JSON."""
# Can be over-ridden by factory constructors. Used for
# serialization/deserialization purposes.
_private_key_pkcs8_pem = None
_private_key_pkcs12 = None
_private_key_password = None
def __init__(self,
service_account_email,
signer,
scopes='',
private_key_id=None,
client_id=None,
user_agent=None,
**kwargs):
super(ServiceAccountCredentials, self).__init__(
None, user_agent=user_agent)
self._service_account_id = service_account_id
self._service_account_email = service_account_email
self._private_key_id = private_key_id
self._private_key = _get_private_key(private_key_pkcs8_text)
self._private_key_pkcs8_text = private_key_pkcs8_text
self._signer = signer
self._scopes = util.scopes_to_string(scopes)
self._private_key_id = private_key_id
self.client_id = client_id
self._user_agent = user_agent
self._token_uri = token_uri
self._revoke_uri = revoke_uri
self._kwargs = kwargs
def _to_json(self, strip, to_serialize=None):
"""Utility function that creates JSON repr. of a credentials object.
Over-ride is needed since PKCS#12 keys will not in general be JSON
serializable.
Args:
strip: array, An array of names of members to exclude from the
JSON.
to_serialize: dict, (Optional) The properties for this object
that will be serialized. This allows callers to modify
before serializing.
Returns:
string, a JSON representation of this instance, suitable to pass to
from_json().
"""
if to_serialize is None:
to_serialize = copy.copy(self.__dict__)
pkcs12_val = to_serialize.get(_PKCS12_KEY)
if pkcs12_val is not None:
to_serialize[_PKCS12_KEY] = base64.b64encode(pkcs12_val)
return super(ServiceAccountCredentials, self)._to_json(
strip, to_serialize=to_serialize)
@classmethod
def _from_parsed_json_keyfile(cls, keyfile_dict, scopes):
"""Helper for factory constructors from JSON keyfile.
Args:
keyfile_dict: dict-like object, The parsed dictionary-like object
containing the contents of the JSON keyfile.
scopes: List or string, Scopes to use when acquiring an
access token.
Returns:
ServiceAccountCredentials, a credentials object created from
the keyfile contents.
Raises:
ValueError, if the credential type is not :data:`SERVICE_ACCOUNT`.
KeyError, if one of the expected keys is not present in
the keyfile.
"""
creds_type = keyfile_dict.get('type')
if creds_type != SERVICE_ACCOUNT:
raise ValueError('Unexpected credentials type', creds_type,
'Expected', SERVICE_ACCOUNT)
service_account_email = keyfile_dict['client_email']
private_key_pkcs8_pem = keyfile_dict['private_key']
private_key_id = keyfile_dict['private_key_id']
client_id = keyfile_dict['client_id']
signer = crypt.Signer.from_string(private_key_pkcs8_pem)
credentials = cls(service_account_email, signer, scopes=scopes,
private_key_id=private_key_id,
client_id=client_id)
credentials._private_key_pkcs8_pem = private_key_pkcs8_pem
return credentials
@classmethod
def from_json_keyfile_name(cls, filename, scopes=''):
"""Factory constructor from JSON keyfile by name.
Args:
filename: string, The location of the keyfile.
scopes: List or string, (Optional) Scopes to use when acquiring an
access token.
Returns:
ServiceAccountCredentials, a credentials object created from
the keyfile.
Raises:
ValueError, if the credential type is not :data:`SERVICE_ACCOUNT`.
KeyError, if one of the expected keys is not present in
the keyfile.
"""
with open(filename, 'r') as file_obj:
client_credentials = json.load(file_obj)
return cls._from_parsed_json_keyfile(client_credentials, scopes)
@classmethod
def from_json_keyfile_dict(cls, keyfile_dict, scopes=''):
"""Factory constructor from parsed JSON keyfile.
Args:
keyfile_dict: dict-like object, The parsed dictionary-like object
containing the contents of the JSON keyfile.
scopes: List or string, (Optional) Scopes to use when acquiring an
access token.
Returns:
ServiceAccountCredentials, a credentials object created from
the keyfile.
Raises:
ValueError, if the credential type is not :data:`SERVICE_ACCOUNT`.
KeyError, if one of the expected keys is not present in
the keyfile.
"""
return cls._from_parsed_json_keyfile(keyfile_dict, scopes)
@classmethod
def _from_p12_keyfile_contents(cls, service_account_email,
private_key_pkcs12,
private_key_password=None, scopes=''):
"""Factory constructor from JSON keyfile.
Args:
service_account_email: string, The email associated with the
service account.
private_key_pkcs12: string, The contents of a PKCS#12 keyfile.
private_key_password: string, (Optional) Password for PKCS#12
private key. Defaults to ``notasecret``.
scopes: List or string, (Optional) Scopes to use when acquiring an
access token.
Returns:
ServiceAccountCredentials, a credentials object created from
the keyfile.
Raises:
NotImplementedError if pyOpenSSL is not installed / not the
active crypto library.
"""
if private_key_password is None:
private_key_password = _PASSWORD_DEFAULT
if crypt.Signer is not crypt.OpenSSLSigner:
raise NotImplementedError(_PKCS12_ERROR)
signer = crypt.Signer.from_string(private_key_pkcs12,
private_key_password)
credentials = cls(service_account_email, signer, scopes=scopes)
credentials._private_key_pkcs12 = private_key_pkcs12
credentials._private_key_password = private_key_password
return credentials
@classmethod
def from_p12_keyfile(cls, service_account_email, filename,
private_key_password=None, scopes=''):
"""Factory constructor from JSON keyfile.
Args:
service_account_email: string, The email associated with the
service account.
filename: string, The location of the PKCS#12 keyfile.
private_key_password: string, (Optional) Password for PKCS#12
private key. Defaults to ``notasecret``.
scopes: List or string, (Optional) Scopes to use when acquiring an
access token.
Returns:
ServiceAccountCredentials, a credentials object created from
the keyfile.
Raises:
NotImplementedError if pyOpenSSL is not installed / not the
active crypto library.
"""
with open(filename, 'rb') as file_obj:
private_key_pkcs12 = file_obj.read()
return cls._from_p12_keyfile_contents(
service_account_email, private_key_pkcs12,
private_key_password=private_key_password, scopes=scopes)
@classmethod
def from_p12_keyfile_buffer(cls, service_account_email, file_buffer,
private_key_password=None, scopes=''):
"""Factory constructor from JSON keyfile.
Args:
service_account_email: string, The email associated with the
service account.
file_buffer: stream, A buffer that implements ``read()``
and contains the PKCS#12 key contents.
private_key_password: string, (Optional) Password for PKCS#12
private key. Defaults to ``notasecret``.
scopes: List or string, (Optional) Scopes to use when acquiring an
access token.
Returns:
ServiceAccountCredentials, a credentials object created from
the keyfile.
Raises:
NotImplementedError if pyOpenSSL is not installed / not the
active crypto library.
"""
private_key_pkcs12 = file_buffer.read()
return cls._from_p12_keyfile_contents(
service_account_email, private_key_pkcs12,
private_key_password=private_key_password, scopes=scopes)
def _generate_assertion(self):
"""Generate the assertion that will be used in the request."""
header = {
'alg': 'RS256',
'typ': 'JWT',
'kid': self._private_key_id
}
now = int(time.time())
payload = {
'aud': self._token_uri,
'aud': self.token_uri,
'scope': self._scopes,
'iat': now,
'exp': now + _ServiceAccountCredentials.MAX_TOKEN_LIFETIME_SECS,
'iss': self._service_account_email
'exp': now + self.MAX_TOKEN_LIFETIME_SECS,
'iss': self._service_account_email,
}
payload.update(self._kwargs)
first_segment = _urlsafe_b64encode(_json_encode(header))
second_segment = _urlsafe_b64encode(_json_encode(payload))
assertion_input = first_segment + b'.' + second_segment
# Sign the assertion.
rsa_bytes = rsa.pkcs1.sign(assertion_input, self._private_key,
'SHA-256')
signature = base64.urlsafe_b64encode(rsa_bytes).rstrip(b'=')
return assertion_input + b'.' + signature
return crypt.make_signed_jwt(self._signer, payload,
key_id=self._private_key_id)
def sign_blob(self, blob):
# Ensure that it is bytes
blob = _to_bytes(blob, encoding='utf-8')
return (self._private_key_id,
rsa.pkcs1.sign(blob, self._private_key, 'SHA-256'))
"""Cryptographically sign a blob (of bytes).
Implements abstract method
:meth:`oauth2client.client.AssertionCredentials.sign_blob`.
Args:
blob: bytes, Message to be signed.
Returns:
tuple, A pair of the private key ID used to sign the blob and
the signed contents.
"""
return self._private_key_id, self._signer.sign(blob)
@property
def service_account_email(self):
"""Get the email for the current service account.
Returns:
string, The email associated with the service account.
"""
return self._service_account_email
@property
def serialization_data(self):
# NOTE: This is only useful for JSON keyfile.
return {
'type': 'service_account',
'client_id': self._service_account_id,
'client_email': self._service_account_email,
'private_key_id': self._private_key_id,
'private_key': self._private_key_pkcs8_text
'private_key': self._private_key_pkcs8_pem,
'client_id': self.client_id,
}
@classmethod
def from_json(cls, json_data):
"""Deserialize a JSON-serialized instance.
Inverse to :meth:`to_json`.
Args:
json_data: dict or string, Serialized JSON (as a string or an
already parsed dictionary) representing a credential.
Returns:
ServiceAccountCredentials from the serialized data.
"""
if not isinstance(json_data, dict):
json_data = json.loads(_from_bytes(json_data))
private_key_pkcs8_pem = None
pkcs12_val = json_data.get(_PKCS12_KEY)
password = None
if pkcs12_val is None:
private_key_pkcs8_pem = json_data['_private_key_pkcs8_pem']
signer = crypt.Signer.from_string(private_key_pkcs8_pem)
else:
# NOTE: This assumes that private_key_pkcs8_pem is not also
# in the serialized data. This would be very incorrect
# state.
pkcs12_val = base64.b64decode(pkcs12_val)
password = json_data['_private_key_password']
signer = crypt.Signer.from_string(pkcs12_val, password)
credentials = cls(
json_data['_service_account_email'],
signer,
scopes=json_data['_scopes'],
private_key_id=json_data['_private_key_id'],
client_id=json_data['client_id'],
user_agent=json_data['_user_agent'],
**json_data['_kwargs']
)
if private_key_pkcs8_pem is not None:
credentials._private_key_pkcs8_pem = private_key_pkcs8_pem
if pkcs12_val is not None:
credentials._private_key_pkcs12 = pkcs12_val
if password is not None:
credentials._private_key_password = password
credentials.invalid = json_data['invalid']
credentials.access_token = json_data['access_token']
credentials.token_uri = json_data['token_uri']
credentials.revoke_uri = json_data['revoke_uri']
token_expiry = json_data.get('token_expiry', None)
if token_expiry is not None:
credentials.token_expiry = datetime.datetime.strptime(
token_expiry, EXPIRY_FORMAT)
return credentials
def create_scoped_required(self):
return not self._scopes
def create_scoped(self, scopes):
return _ServiceAccountCredentials(self._service_account_id,
self._service_account_email,
self._private_key_id,
self._private_key_pkcs8_text,
scopes,
user_agent=self._user_agent,
token_uri=self._token_uri,
revoke_uri=self._revoke_uri,
**self._kwargs)
result = self.__class__(self._service_account_email,
self._signer,
scopes=scopes,
private_key_id=self._private_key_id,
client_id=self.client_id,
user_agent=self._user_agent,
**self._kwargs)
result.token_uri = self.token_uri
result.revoke_uri = self.revoke_uri
result._private_key_pkcs8_pem = self._private_key_pkcs8_pem
result._private_key_pkcs12 = self._private_key_pkcs12
result._private_key_password = self._private_key_password
return result
def create_delegated(self, sub):
"""Create credentials that act as domain-wide delegation of authority.
def _get_private_key(private_key_pkcs8_text):
"""Get an RSA private key object from a pkcs8 representation."""
private_key_pkcs8_text = _to_bytes(private_key_pkcs8_text)
der = rsa.pem.load_pem(private_key_pkcs8_text, 'PRIVATE KEY')
asn1_private_key, _ = decoder.decode(der, asn1Spec=PrivateKeyInfo())
return rsa.PrivateKey.load_pkcs1(
asn1_private_key.getComponentByName('privateKey').asOctets(),
format='DER')
Use the ``sub`` parameter as the subject to delegate on behalf of
that user.
For example::
>>> account_sub = 'foo@email.com'
>>> delegate_creds = creds.create_delegated(account_sub)
Args:
sub: string, An email address that this service account will
act on behalf of (via domain-wide delegation).
Returns:
ServiceAccountCredentials, a copy of the current service account
updated to act on behalf of ``sub``.
"""
new_kwargs = dict(self._kwargs)
new_kwargs['sub'] = sub
result = self.__class__(self._service_account_email,
self._signer,
scopes=self._scopes,
private_key_id=self._private_key_id,
client_id=self.client_id,
user_agent=self._user_agent,
**new_kwargs)
result.token_uri = self.token_uri
result.revoke_uri = self.revoke_uri
result._private_key_pkcs8_pem = self._private_key_pkcs8_pem
result._private_key_pkcs12 = self._private_key_pkcs12
result._private_key_password = self._private_key_password
return result

View File

@ -26,6 +26,7 @@ import socket
import sys
from six.moves import BaseHTTPServer
from six.moves import http_client
from six.moves import urllib
from six.moves import input
@ -95,7 +96,7 @@ class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler):
if the flow has completed. Note that we can't detect
if an error occurred.
"""
self.send_response(200)
self.send_response(http_client.OK)
self.send_header("Content-type", "text/html")
self.end_headers()
query = self.path.split('?', 1)[-1]
@ -112,7 +113,7 @@ class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler):
@util.positional(3)
def run_flow(flow, storage, flags, http=None):
def run_flow(flow, storage, flags=None, http=None):
"""Core code for a command-line application.
The ``run()`` function is called from your application and runs
@ -153,15 +154,18 @@ def run_flow(flow, storage, flags, http=None):
Args:
flow: Flow, an OAuth 2.0 Flow to step through.
storage: Storage, a ``Storage`` to store the credential in.
flags: ``argparse.Namespace``, The command-line flags. This is the
object returned from calling ``parse_args()`` on
``argparse.ArgumentParser`` as described above.
flags: ``argparse.Namespace``, (Optional) The command-line flags. This
is the object returned from calling ``parse_args()`` on
``argparse.ArgumentParser`` as described above. Defaults
to ``argparser.parse_args()``.
http: An instance of ``httplib2.Http.request`` or something that
acts like it.
Returns:
Credentials, the obtained credential.
"""
if flags is None:
flags = argparser.parse_args()
logging.getLogger().setLevel(getattr(logging, flags.logging_level))
if not flags.noauth_local_webserver:
success = False
@ -211,8 +215,11 @@ def run_flow(flow, storage, flags, http=None):
print()
print(' ' + authorize_url)
print()
print('If your browser is on a different machine then exit and re-run this')
print('after creating a file called nobrowser.txt in the same path as GAM.')
print('If your browser is on a different machine then '
'exit and re-run this')
print('application with the command-line parameter ')
print()
print(' --noauth_local_webserver')
print()
else:
print('Go to the following link in your browser:')

View File

@ -1,5 +1,3 @@
#!/usr/bin/env python
#
# Copyright 2014 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,15 +11,12 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Common utility library."""
import functools
import inspect
import logging
import sys
import types
import six
from six.moves import urllib
@ -188,21 +183,6 @@ def string_to_scopes(scopes):
return scopes
def dict_to_tuple_key(dictionary):
"""Converts a dictionary to a tuple that can be used as an immutable key.
The resulting key is always sorted so that logically equivalent
dictionaries always produce an identical tuple for a key.
Args:
dictionary: the dictionary to use as the key.
Returns:
A tuple representing the dictionary in it's naturally sorted ordering.
"""
return tuple(sorted(dictionary.items()))
def _add_query_parameter(url, name, value):
"""Adds a query parameter to a url.