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