1 #region Copyright notice and license
2 // Copyright 2015 gRPC authors.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //     http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 #endregion
16 using System;
17 using System.Runtime.InteropServices;
18 using System.Threading;
19 using System.Threading.Tasks;
20 
21 using Grpc.Core.Logging;
22 using Grpc.Core.Utils;
23 
24 namespace Grpc.Core.Internal
25 {
26     internal class NativeMetadataCredentialsPlugin
27     {
28         const string GetMetadataExceptionStatusMsg = "Exception occurred in metadata credentials plugin.";
29         const string GetMetadataExceptionLogMsg = GetMetadataExceptionStatusMsg + " This is likely not a problem with gRPC itself. Please verify that the code supplying the metadata (usually an authentication token) works correctly.";
30         static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<NativeMetadataCredentialsPlugin>();
31         static readonly NativeMethods Native = NativeMethods.Get();
32 
33         AsyncAuthInterceptor interceptor;
34         CallCredentialsSafeHandle credentials;
35         NativeCallbackRegistration callbackRegistration;
36 
NativeMetadataCredentialsPlugin(AsyncAuthInterceptor interceptor)37         public NativeMetadataCredentialsPlugin(AsyncAuthInterceptor interceptor)
38         {
39             this.interceptor = GrpcPreconditions.CheckNotNull(interceptor, "interceptor");
40             this.callbackRegistration = NativeCallbackDispatcher.RegisterCallback(HandleUniversalCallback);
41             this.credentials = Native.grpcsharp_metadata_credentials_create_from_plugin(this.callbackRegistration.Tag);
42         }
43 
44         public CallCredentialsSafeHandle Credentials
45         {
46             get { return credentials; }
47         }
48 
HandleUniversalCallback(IntPtr arg0, IntPtr arg1, IntPtr arg2, IntPtr arg3, IntPtr arg4, IntPtr arg5)49         private int HandleUniversalCallback(IntPtr arg0, IntPtr arg1, IntPtr arg2, IntPtr arg3, IntPtr arg4, IntPtr arg5)
50         {
51             NativeMetadataInterceptorHandler(arg0, arg1, arg2, arg3, arg4 != IntPtr.Zero);
52             return 0;
53         }
54 
NativeMetadataInterceptorHandler(IntPtr serviceUrlPtr, IntPtr methodNamePtr, IntPtr callbackPtr, IntPtr userDataPtr, bool isDestroy)55         private void NativeMetadataInterceptorHandler(IntPtr serviceUrlPtr, IntPtr methodNamePtr, IntPtr callbackPtr, IntPtr userDataPtr, bool isDestroy)
56         {
57             if (isDestroy)
58             {
59                 this.callbackRegistration.Dispose();
60                 return;
61             }
62 
63             try
64             {
65                 // NOTE: The serviceUrlPtr and methodNamePtr values come from the grpc_auth_metadata_context
66                 // and are only guaranteed to be valid until synchronous return of this handler.
67                 // We are effectively making a copy of these strings by creating C# string from the native char pointers,
68                 // and passing the resulting C# strings to the context is therefore safe.
69                 var context = new AuthInterceptorContext(Marshal.PtrToStringAnsi(serviceUrlPtr), Marshal.PtrToStringAnsi(methodNamePtr));
70                 // Make a guarantee that credentials_notify_from_plugin is invoked async to be compliant with c-core API.
71                 ThreadPool.QueueUserWorkItem(async (stateInfo) => await GetMetadataAsync(context, callbackPtr, userDataPtr));
72             }
73             catch (Exception e)
74             {
75                 // eat the exception, we must not throw when inside callback from native code.
76                 Logger.Error(e, "Exception occurred while invoking native metadata interceptor handler.");
77             }
78         }
79 
GetMetadataAsync(AuthInterceptorContext context, IntPtr callbackPtr, IntPtr userDataPtr)80         private async Task GetMetadataAsync(AuthInterceptorContext context, IntPtr callbackPtr, IntPtr userDataPtr)
81         {
82             try
83             {
84                 var metadata = new Metadata();
85                 await interceptor(context, metadata).ConfigureAwait(false);
86 
87                 using (var metadataArray = MetadataArraySafeHandle.Create(metadata))
88                 {
89                     Native.grpcsharp_metadata_credentials_notify_from_plugin(callbackPtr, userDataPtr, metadataArray, StatusCode.OK, null);
90                 }
91             }
92             catch (Exception e)
93             {
94                 string detail = GetMetadataExceptionStatusMsg + " " + e.ToString();
95                 Native.grpcsharp_metadata_credentials_notify_from_plugin(callbackPtr, userDataPtr, MetadataArraySafeHandle.Create(Metadata.Empty), StatusCode.Unknown, detail);
96                 Logger.Error(e, GetMetadataExceptionLogMsg);
97             }
98         }
99     }
100 }
101