mirror of https://github.com/grpc/grpc.git
Merge pull request #19456 from lidizheng/example-auth
[Python] Add authentication extension examplepull/19485/head
commit
25c720f650
9 changed files with 545 additions and 0 deletions
@ -0,0 +1,65 @@ |
||||
# Copyright 2019 The gRPC Authors |
||||
# |
||||
# 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. |
||||
|
||||
filegroup( |
||||
name = "_credentials_files", |
||||
testonly = 1, |
||||
srcs = [ |
||||
"credentials/localhost.key", |
||||
"credentials/localhost.crt", |
||||
"credentials/root.crt", |
||||
], |
||||
) |
||||
|
||||
py_library( |
||||
name = "_credentials", |
||||
testonly = 1, |
||||
srcs = ["_credentials.py"], |
||||
data = [":_credentials_files"], |
||||
) |
||||
|
||||
py_binary( |
||||
name = "customized_auth_client", |
||||
testonly = 1, |
||||
srcs = ["customized_auth_client.py"], |
||||
deps = [ |
||||
":_credentials", |
||||
"//src/python/grpcio/grpc:grpcio", |
||||
"//examples:py_helloworld", |
||||
], |
||||
) |
||||
|
||||
py_binary( |
||||
name = "customized_auth_server", |
||||
testonly = 1, |
||||
srcs = ["customized_auth_server.py"], |
||||
deps = [ |
||||
":_credentials", |
||||
"//src/python/grpcio/grpc:grpcio", |
||||
"//examples:py_helloworld", |
||||
|
||||
], |
||||
) |
||||
|
||||
py_test( |
||||
name = "_auth_example_test", |
||||
srcs = ["test/_auth_example_test.py"], |
||||
deps = [ |
||||
"//src/python/grpcio/grpc:grpcio", |
||||
"//examples:py_helloworld", |
||||
":customized_auth_client", |
||||
":customized_auth_server", |
||||
":_credentials", |
||||
], |
||||
) |
@ -0,0 +1,112 @@ |
||||
# Authentication Extension Example in gRPC Python |
||||
|
||||
## Check Our Guide First |
||||
|
||||
For most common usage of authentication in gRPC Python, please see our |
||||
[Authentication](https://grpc.io/docs/guides/auth/) guide's Python section. The |
||||
Guide includes following scenarios: |
||||
|
||||
1. Server SSL credential setup |
||||
2. Client SSL credential setup |
||||
3. Authenticate with Google using a JWT |
||||
4. Authenticate with Google using an Oauth2 token |
||||
|
||||
Also, the guide talks about gRPC specific credential types. |
||||
|
||||
### Channel credentials |
||||
|
||||
Channel credentials are attached to a `Channel` object, the most common use case |
||||
are SSL credentials. |
||||
|
||||
### Call credentials |
||||
|
||||
Call credentials are attached to a `Call` object (corresponding to an RPC). |
||||
Under the hood, the call credentials is a function that takes in information of |
||||
the RPC and modify metadata through callback. |
||||
|
||||
## About This Example |
||||
|
||||
This example focuses on extending gRPC authentication mechanism: |
||||
1) Customize authentication plugin; |
||||
2) Composite client side credentials; |
||||
3) Validation through interceptor on server side. |
||||
|
||||
## AuthMetadataPlugin: Manipulate metadata for each call |
||||
|
||||
Unlike TLS/SSL based authentication, the authentication extension in gRPC Python |
||||
lives at a much higher level of networking. It relies on the transmission of |
||||
metadata (HTTP Header) between client and server, instead of alternating the |
||||
transport protocol. |
||||
|
||||
gRPC Python provides a way to intercept an RPC and append authentication related |
||||
metadata through |
||||
[`AuthMetadataPlugin`](https://grpc.github.io/grpc/python/grpc.html#grpc.AuthMetadataPlugin). |
||||
Those in need of a custom authentication method may simply provide a concrete |
||||
implementation of the following interface: |
||||
|
||||
```Python |
||||
class AuthMetadataPlugin: |
||||
"""A specification for custom authentication.""" |
||||
|
||||
def __call__(self, context, callback): |
||||
"""Implements authentication by passing metadata to a callback. |
||||
|
||||
Implementations of this method must not block. |
||||
|
||||
Args: |
||||
context: An AuthMetadataContext providing information on the RPC that |
||||
the plugin is being called to authenticate. |
||||
callback: An AuthMetadataPluginCallback to be invoked either |
||||
synchronously or asynchronously. |
||||
""" |
||||
``` |
||||
|
||||
Then pass the instance of the concrete implementation to |
||||
`grpc.metadata_call_credentials` function to be converted into a |
||||
`CallCredentials` object. Please NOTE that it is possible to pass a Python |
||||
function object directly, but we recommend to inherit from the base class to |
||||
ensure implementation correctness. |
||||
|
||||
|
||||
```Python |
||||
def metadata_call_credentials(metadata_plugin, name=None): |
||||
"""Construct CallCredentials from an AuthMetadataPlugin. |
||||
|
||||
Args: |
||||
metadata_plugin: An AuthMetadataPlugin to use for authentication. |
||||
name: An optional name for the plugin. |
||||
|
||||
Returns: |
||||
A CallCredentials. |
||||
""" |
||||
``` |
||||
|
||||
The `CallCredentials` object can be passed directly into an RPC like: |
||||
|
||||
```Python |
||||
call_credentials = grpc.metadata_call_credentials(my_foo_plugin) |
||||
stub.FooRpc(request, credentials=call_credentials) |
||||
``` |
||||
|
||||
Or you can use `ChannelCredentials` and `CallCredentials` at the same time by |
||||
combining them: |
||||
|
||||
```Python |
||||
channel_credentials = ... |
||||
call_credentials = ... |
||||
composite_credentials = grpc.composite_channel_credentials( |
||||
channel_credential, |
||||
call_credentials) |
||||
channel = grpc.secure_channel(server_address, composite_credentials) |
||||
``` |
||||
|
||||
It is also possible to apply multiple `CallCredentials` to a single RPC: |
||||
|
||||
```Python |
||||
call_credentials_foo = ... |
||||
call_credentials_bar = ... |
||||
call_credentials = grpc.composite_call_credentials( |
||||
call_credentials_foo, |
||||
call_credentials_bar) |
||||
stub.FooRpc(request, credentials=call_credentials) |
||||
``` |
@ -0,0 +1,31 @@ |
||||
# Copyright 2019 The gRPC Authors |
||||
# |
||||
# 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. |
||||
"""Loading SSL credentials for gRPC Python authentication example.""" |
||||
|
||||
from __future__ import absolute_import |
||||
from __future__ import division |
||||
from __future__ import print_function |
||||
|
||||
import os |
||||
|
||||
|
||||
def _load_credential_from_file(filepath): |
||||
real_path = os.path.join(os.path.dirname(__file__), filepath) |
||||
with open(real_path, 'rb') as f: |
||||
return f.read() |
||||
|
||||
|
||||
SERVER_CERTIFICATE = _load_credential_from_file('credentials/localhost.crt') |
||||
SERVER_CERTIFICATE_KEY = _load_credential_from_file('credentials/localhost.key') |
||||
ROOT_CERTIFICATE = _load_credential_from_file('credentials/root.crt') |
@ -0,0 +1,19 @@ |
||||
-----BEGIN CERTIFICATE----- |
||||
MIIDFjCCAf4CCQCzrLIhrWa55zANBgkqhkiG9w0BAQsFADBCMQswCQYDVQQGEwJV |
||||
UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEPMA0GA1UECgwGR29vZ2xlMQ0wCwYDVQQL |
||||
DARnUlBDMCAXDTE5MDYyNDIyMjIzM1oYDzIxMTkwNTMxMjIyMjMzWjBWMQswCQYD |
||||
VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEPMA0GA1UECgwGR29vZ2xlMQ0w |
||||
CwYDVQQLDARnUlBDMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB |
||||
AQUAA4IBDwAwggEKAoIBAQCtCW0TjugnIUu8BEVIYvdMP+/2GENQDjZhZ8eKR5C6 |
||||
toDGbgjsDtt/GxISAg4cg70fIvy0XolnGPZodvfHDM4lJ7yHBOdZD8TXQoE6okR7 |
||||
HZuLUJ20M0pXgWqtRewKRUjuYsSDXBnzLiZw1dcv9nGpo+Bqa8NonpiGRRpEkshF |
||||
D6T9KU9Ts/x+wMQBIra2Gj0UMh79jPhUuxcYAQA0JQGivnOtdwuPiumpnUT8j8h6 |
||||
tWg5l01EsCZWJecCF85KnGpJEVYPyPqBqGsy0nGS9plGotOWF87+jyUQt+KD63xA |
||||
aBmTro86mKDDKEK4JvzjVeMGz2UbVcLPiiZnErTFaiXJAgMBAAEwDQYJKoZIhvcN |
||||
AQELBQADggEBAKsDgOPCWp5WCy17vJbRlgfgk05sVNIHZtzrmdswjBmvSg8MUpep |
||||
XqcPNUpsljAXsf9UM5IFEMRdilUsFGWvHjBEtNAW8WUK9UV18WRuU//0w1Mp5HAN |
||||
xUEKb4BoyZr65vlCnTR+AR5c9FfPvLibhr5qHs2RA8Y3GyLOcGqBWed87jhdQLCc |
||||
P1bxB+96le5JeXq0tw215lxonI2/3ZYVK4/ok9gwXrQoWm8YieJqitk/ZQ4S17/4 |
||||
pynHtDfdxLn23EXeGx+UTxJGfpRmhEZdJ+MN7QGYoomzx5qS5XoYKxRNrDlirJpr |
||||
OqXIn8E1it+6d5gOZfuHawcNGhRLplE/pfA= |
||||
-----END CERTIFICATE----- |
@ -0,0 +1,27 @@ |
||||
-----BEGIN RSA PRIVATE KEY----- |
||||
MIIEogIBAAKCAQEArQltE47oJyFLvARFSGL3TD/v9hhDUA42YWfHikeQuraAxm4I |
||||
7A7bfxsSEgIOHIO9HyL8tF6JZxj2aHb3xwzOJSe8hwTnWQ/E10KBOqJEex2bi1Cd |
||||
tDNKV4FqrUXsCkVI7mLEg1wZ8y4mcNXXL/ZxqaPgamvDaJ6YhkUaRJLIRQ+k/SlP |
||||
U7P8fsDEASK2tho9FDIe/Yz4VLsXGAEANCUBor5zrXcLj4rpqZ1E/I/IerVoOZdN |
||||
RLAmViXnAhfOSpxqSRFWD8j6gahrMtJxkvaZRqLTlhfO/o8lELfig+t8QGgZk66P |
||||
OpigwyhCuCb841XjBs9lG1XCz4omZxK0xWolyQIDAQABAoIBADeq/Kh6JT3RfGf0 |
||||
h8WN8TlaqHxnueAbcmtL0+oss+cdp7gu1jf7X6o4r0uT1a5ew40s2Fe+wj2kzkE1 |
||||
ZOlouTlC22gkr7j7Vbxa7PBMG/Pvxoa/XL0IczZLsGImSJXVTG1E4SvRiZeulTdf |
||||
1GbdxhtpWV1jZe5Wd4Na3+SHxF5S7m3PrHiZlYdz1ND+8XZs1NlL9+ej72qSFul9 |
||||
t/QjMWJ9pky/Wad5abnRLRyOsg+BsgnXbkUy2rD89ZxFMLda9pzXo3TPyAlBHonr |
||||
mkEsE4eRMWMpjBM79JbeyDdHn/cs/LjAZrzeDf7ugXr2CHQpKaM5O0PsNHezJII9 |
||||
L5kCfzECgYEA4M/rz1UP1/BJoSqigUlSs0tPAg8a5UlkVsh6Osuq72IPNo8qg/Fw |
||||
oV/IiIS+q+obRcFj1Od3PGdTpCJwW5dzd2fXBQGmGdj0HucnCrs13RtBh91JiF5i |
||||
y/YYI9KfgOG2ZT9gG68T0gTs6jRrS3Qd83npqjrkJqMOd7s00MK9tUcCgYEAxQq7 |
||||
T541oCYHSBRIIb0IrR25krZy9caxzCqPDwOcuuhaCqCiaq+ATvOWlSfgecm4eH0K |
||||
PCH0xlWxG0auPEwm4pA8+/WR/XJwscPZMuoht1EoKy1his4eKx/s7hHNeO6KOF0V |
||||
Y/zqIiuZnEwUoKbn7EqqNFSTT65PJKyGsICJFG8CgYAfaw9yl1myfQNdQb8aQGwN |
||||
YJ33FLNWje427qeeZe5KrDKiFloDvI9YDjHRWnPnRL1w/zj7fSm9yFb5HlMDieP6 |
||||
MQnsyjEzdY2QcA+VwVoiv3dmDHgFVeOKy6bOAtaFxYWfGr9MvygO9t9BT/gawGyb |
||||
JVORlc9i0vDnrMMR1dV7awKBgBpTWLtGc/u1mPt0Wj7HtsUKV6TWY32a0l5owTxM |
||||
S0BdksogtBJ06DukJ9Y9wawD23WdnyRxlPZ6tHLkeprrwbY7dypioOKvy4a0l+xJ |
||||
g7+uRCOgqIuXBkjUtx8HmeAyXp0xMo5tWArAsIFFWOwt4IadYygitJvMuh44PraO |
||||
NcJZAoGADEiV0dheXUCVr8DrtSom8DQMj92/G/FIYjXL8OUhh0+F+YlYP0+F8PEU |
||||
yYIWEqL/S5tVKYshimUXQa537JcRKsTVJBG/ZKD2kuqgOc72zQy3oplimXeJDCXY |
||||
h2eAQ0u8GN6tN9C4t8Kp4a3y6FGsxgu+UTxdnL3YQ+yHAVhtCzo= |
||||
-----END RSA PRIVATE KEY----- |
@ -0,0 +1,20 @@ |
||||
-----BEGIN CERTIFICATE----- |
||||
MIIDWTCCAkGgAwIBAgIJAPOConZMwykwMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNV |
||||
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMQ8wDQYDVQQKDAZHb29nbGUxDTAL |
||||
BgNVBAsMBGdSUEMwIBcNMTkwNjI0MjIyMDA3WhgPMjExOTA1MzEyMjIwMDdaMEIx |
||||
CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMQ8wDQYDVQQKDAZHb29n |
||||
bGUxDTALBgNVBAsMBGdSUEMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB |
||||
AQCwqei3TfyLidnQNDJ2lierMYo229K92DuORni7nSjJQ59Jc3dNMsmqGQJjCD8o |
||||
6mTlKM/oCbs27Wpx+OxcOLvT95j2kiDGca1fCvaMdguIod09SWiyMpv/hp0trLv7 |
||||
NJIKHznath6rHYX2Ii3fZ1yCPzyQbEPSAA+GNpoNm1v1ZWmWKke9v7vLlS3inNlW |
||||
Mt9jepK7DrtbNZnVDjeItnppBSbVYRMxIyNHkepFbqXx5TpkCvl4M4XQZw9bfSxQ |
||||
i3WZ3q+T1Tw//OUdPNc+OfMhu0MA0QoMwikskP0NaIC3dbJZ5Ogx0RcnaB4E+9C6 |
||||
O/znUEh3WuKVl5HXBF+UwWoFAgMBAAGjUDBOMB0GA1UdDgQWBBRm3JIgzgK4G97J |
||||
fbMGatWMZc7V3jAfBgNVHSMEGDAWgBRm3JIgzgK4G97JfbMGatWMZc7V3jAMBgNV |
||||
HRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCNiV8x41if094ry2srS0YucpiN |
||||
3rTPk08FOLsENTMYai524TGXJti1P6ofGr5KXCL0uxTByHE3fEiMMud2TIY5iHQo |
||||
Y4mzDTTcb+Q7yKHwYZMlcp6nO8W+NeY5t+S0JPHhb8deKWepcN2UpXBUYQLw7AiE |
||||
l96T9Gi+vC9h/XE5IVwHFQXTxf5UYzXtW1nfapvrOONg/ms41dgmrRKIi+knWfiJ |
||||
FdHpHX2sfDAoJtnpEISX+nxRGNVTLY64utXWm4yxaZJshvy2s8zWJgRg7rtwAhTT |
||||
Np9E9MnihXLEmDI4Co9XlLPJyZFmqImsbmVuKFeQOCiLAoPJaMI2lbi7fiTo |
||||
-----END CERTIFICATE----- |
@ -0,0 +1,105 @@ |
||||
# Copyright 2019 The gRPC Authors |
||||
# |
||||
# 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. |
||||
"""Client of the Python example of customizing authentication mechanism.""" |
||||
|
||||
from __future__ import absolute_import |
||||
from __future__ import division |
||||
from __future__ import print_function |
||||
|
||||
import argparse |
||||
import contextlib |
||||
import logging |
||||
|
||||
import grpc |
||||
from examples import helloworld_pb2 |
||||
from examples import helloworld_pb2_grpc |
||||
from examples.python.auth import _credentials |
||||
|
||||
_LOGGER = logging.getLogger(__name__) |
||||
_LOGGER.setLevel(logging.INFO) |
||||
|
||||
_ONE_DAY_IN_SECONDS = 60 * 60 * 24 |
||||
|
||||
_SERVER_ADDR_TEMPLATE = 'localhost:%d' |
||||
_SIGNATURE_HEADER_KEY = 'x-signature' |
||||
|
||||
|
||||
class AuthGateway(grpc.AuthMetadataPlugin): |
||||
|
||||
def __call__(self, context, callback): |
||||
"""Implements authentication by passing metadata to a callback. |
||||
|
||||
Implementations of this method must not block. |
||||
|
||||
Args: |
||||
context: An AuthMetadataContext providing information on the RPC that |
||||
the plugin is being called to authenticate. |
||||
callback: An AuthMetadataPluginCallback to be invoked either |
||||
synchronously or asynchronously. |
||||
""" |
||||
# Example AuthMetadataContext object: |
||||
# AuthMetadataContext( |
||||
# service_url=u'https://localhost:50051/helloworld.Greeter', |
||||
# method_name=u'SayHello') |
||||
signature = context.method_name[::-1] |
||||
callback(((_SIGNATURE_HEADER_KEY, signature),), None) |
||||
|
||||
|
||||
@contextlib.contextmanager |
||||
def create_client_channel(addr): |
||||
# Call credential object will be invoked for every single RPC |
||||
call_credentials = grpc.metadata_call_credentials( |
||||
AuthGateway(), name='auth gateway') |
||||
# Channel credential will be valid for the entire channel |
||||
channel_credential = grpc.ssl_channel_credentials( |
||||
_credentials.ROOT_CERTIFICATE) |
||||
# Combining channel credentials and call credentials together |
||||
composite_credentials = grpc.composite_channel_credentials( |
||||
channel_credential, |
||||
call_credentials, |
||||
) |
||||
channel = grpc.secure_channel(addr, composite_credentials) |
||||
yield channel |
||||
|
||||
|
||||
def send_rpc(channel): |
||||
stub = helloworld_pb2_grpc.GreeterStub(channel) |
||||
request = helloworld_pb2.HelloRequest(name='you') |
||||
try: |
||||
response = stub.SayHello(request) |
||||
except grpc.RpcError as rpc_error: |
||||
_LOGGER.error('Received error: %s', rpc_error) |
||||
return rpc_error |
||||
else: |
||||
_LOGGER.info('Received message: %s', response) |
||||
return response |
||||
|
||||
|
||||
def main(): |
||||
parser = argparse.ArgumentParser() |
||||
parser.add_argument( |
||||
'--port', |
||||
nargs='?', |
||||
type=int, |
||||
default=50051, |
||||
help='the address of server') |
||||
args = parser.parse_args() |
||||
|
||||
with create_client_channel(_SERVER_ADDR_TEMPLATE % args.port) as channel: |
||||
send_rpc(channel) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
logging.basicConfig(level=logging.INFO) |
||||
main() |
@ -0,0 +1,110 @@ |
||||
# Copyright 2019 The gRPC Authors |
||||
# |
||||
# 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. |
||||
"""Server of the Python example of customizing authentication mechanism.""" |
||||
|
||||
from __future__ import absolute_import |
||||
from __future__ import division |
||||
from __future__ import print_function |
||||
|
||||
import argparse |
||||
import contextlib |
||||
import logging |
||||
import time |
||||
from concurrent import futures |
||||
|
||||
import grpc |
||||
from examples import helloworld_pb2 |
||||
from examples import helloworld_pb2_grpc |
||||
from examples.python.auth import _credentials |
||||
|
||||
_LOGGER = logging.getLogger(__name__) |
||||
_LOGGER.setLevel(logging.INFO) |
||||
|
||||
_ONE_DAY_IN_SECONDS = 60 * 60 * 24 |
||||
|
||||
_LISTEN_ADDRESS_TEMPLATE = 'localhost:%d' |
||||
_SIGNATURE_HEADER_KEY = 'x-signature' |
||||
|
||||
|
||||
class SignatureValidationInterceptor(grpc.ServerInterceptor): |
||||
|
||||
def __init__(self): |
||||
|
||||
def abort(ignored_request, context): |
||||
context.abort(grpc.StatusCode.UNAUTHENTICATED, 'Invalid signature') |
||||
|
||||
self._abortion = grpc.unary_unary_rpc_method_handler(abort) |
||||
|
||||
def intercept_service(self, continuation, handler_call_details): |
||||
# Example HandlerCallDetails object: |
||||
# _HandlerCallDetails( |
||||
# method=u'/helloworld.Greeter/SayHello', |
||||
# invocation_metadata=...) |
||||
method_name = handler_call_details.method.split('/')[-1] |
||||
expected_metadata = (_SIGNATURE_HEADER_KEY, method_name[::-1]) |
||||
if expected_metadata in handler_call_details.invocation_metadata: |
||||
return continuation(handler_call_details) |
||||
else: |
||||
return self._abortion |
||||
|
||||
|
||||
class SimpleGreeter(helloworld_pb2_grpc.GreeterServicer): |
||||
|
||||
def SayHello(self, request, unused_context): |
||||
return helloworld_pb2.HelloReply(message='Hello, %s!' % request.name) |
||||
|
||||
|
||||
@contextlib.contextmanager |
||||
def run_server(port): |
||||
# Bind interceptor to server |
||||
server = grpc.server( |
||||
futures.ThreadPoolExecutor(), |
||||
interceptors=(SignatureValidationInterceptor(),)) |
||||
helloworld_pb2_grpc.add_GreeterServicer_to_server(SimpleGreeter(), server) |
||||
|
||||
# Loading credentials |
||||
server_credentials = grpc.ssl_server_credentials((( |
||||
_credentials.SERVER_CERTIFICATE_KEY, |
||||
_credentials.SERVER_CERTIFICATE, |
||||
),)) |
||||
|
||||
# Pass down credentials |
||||
port = server.add_secure_port(_LISTEN_ADDRESS_TEMPLATE % port, |
||||
server_credentials) |
||||
|
||||
server.start() |
||||
try: |
||||
yield port |
||||
finally: |
||||
server.stop(0) |
||||
|
||||
|
||||
def main(): |
||||
parser = argparse.ArgumentParser() |
||||
parser.add_argument( |
||||
'--port', nargs='?', type=int, default=50051, help='the listening port') |
||||
args = parser.parse_args() |
||||
|
||||
with run_server(args.port) as port: |
||||
logging.info('Server is listening at port :%d', port) |
||||
try: |
||||
while True: |
||||
time.sleep(_ONE_DAY_IN_SECONDS) |
||||
except KeyboardInterrupt: |
||||
pass |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
logging.basicConfig(level=logging.INFO) |
||||
main() |
@ -0,0 +1,56 @@ |
||||
# Copyright 2019 The gRPC Authors |
||||
# |
||||
# 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. |
||||
"""Test for gRPC Python authentication example.""" |
||||
|
||||
from __future__ import absolute_import |
||||
from __future__ import division |
||||
from __future__ import print_function |
||||
|
||||
import unittest |
||||
|
||||
import grpc |
||||
from examples.python.auth import _credentials |
||||
from examples.python.auth import customized_auth_client |
||||
from examples.python.auth import customized_auth_server |
||||
|
||||
_SERVER_ADDR_TEMPLATE = 'localhost:%d' |
||||
|
||||
|
||||
class AuthExampleTest(unittest.TestCase): |
||||
|
||||
def test_successful_call(self): |
||||
with customized_auth_server.run_server(0) as port: |
||||
with customized_auth_client.create_client_channel( |
||||
_SERVER_ADDR_TEMPLATE % port) as channel: |
||||
customized_auth_client.send_rpc(channel) |
||||
# No unhandled exception raised, test passed! |
||||
|
||||
def test_no_channel_credential(self): |
||||
with customized_auth_server.run_server(0) as port: |
||||
with grpc.insecure_channel(_SERVER_ADDR_TEMPLATE % port) as channel: |
||||
resp = customized_auth_client.send_rpc(channel) |
||||
self.assertEqual(resp.code(), grpc.StatusCode.UNAVAILABLE) |
||||
|
||||
def test_no_call_credential(self): |
||||
with customized_auth_server.run_server(0) as port: |
||||
channel_credential = grpc.ssl_channel_credentials( |
||||
_credentials.ROOT_CERTIFICATE) |
||||
with grpc.secure_channel(_SERVER_ADDR_TEMPLATE % port, |
||||
channel_credential) as channel: |
||||
resp = customized_auth_client.send_rpc(channel) |
||||
self.assertEqual(resp.code(), grpc.StatusCode.UNAUTHENTICATED) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
unittest.main(verbosity=2) |
Loading…
Reference in new issue