diff options
Diffstat (limited to 'src')
14 files changed, 86 insertions, 48 deletions
diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth/InMemoryOAuthTokenManager.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/InMemoryOAuthTokenManager.cs index e5fd67f..a97e5d8 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth/InMemoryOAuthTokenManager.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth/InMemoryOAuthTokenManager.cs @@ -7,6 +7,8 @@ namespace DotNetOpenAuth.AspNet.Clients { using System; using System.Collections.Generic; + using System.Threading; + using System.Web; using DotNetOpenAuth.OAuth; using DotNetOpenAuth.OAuth.ChannelElements; using DotNetOpenAuth.OAuth.Messages; @@ -92,9 +94,9 @@ namespace DotNetOpenAuth.AspNet.Clients { /// useful in an ASP.NET web application within the implementation of this method. /// Alternatively you may store the access token here without associating with a user account, /// and wait until - /// <see cref="WebConsumer.ProcessUserAuthorizationAsync()"/> + /// <see cref="WebConsumer.ProcessUserAuthorizationAsync(HttpRequestBase, CancellationToken)"/> /// or - /// <see cref="DesktopConsumer.ProcessUserAuthorizationAsync(string, string)"/> + /// <see cref="DesktopConsumer.ProcessUserAuthorizationAsync(string, string, CancellationToken)"/> /// return the access /// token to associate the access token with a user account at that point. /// </para> diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/OAuth2Client.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/OAuth2Client.cs index 6e49d51..0765a9e 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/OAuth2Client.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/OAuth2Client.cs @@ -11,6 +11,9 @@ namespace DotNetOpenAuth.AspNet.Clients { using System.Threading; using System.Threading.Tasks; using System.Web; + + using DotNetOpenAuth.Messaging; + using Validation; /// <summary> @@ -65,12 +68,13 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <returns> /// A task that completes with the asynchronous operation. /// </returns> - public virtual async Task RequestAuthenticationAsync(HttpContextBase context, Uri returnUrl, CancellationToken cancellationToken = default(CancellationToken)) { + public virtual Task RequestAuthenticationAsync(HttpContextBase context, Uri returnUrl, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(context, "context"); Requires.NotNull(returnUrl, "returnUrl"); string redirectUrl = this.GetServiceLoginUrl(returnUrl).AbsoluteUri; context.Response.Redirect(redirectUrl, endResponse: true); + return MessagingUtilities.CompletedTask; } /// <summary> diff --git a/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj b/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj index 6537c05..7877cf0 100644 --- a/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj +++ b/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj @@ -29,6 +29,7 @@ <Compile Include="Messaging\Bindings\ICryptoKeyStore.cs" /> <Compile Include="Messaging\Bindings\MemoryCryptoKeyStore.cs" /> <Compile Include="Messaging\BinaryDataBagFormatter.cs" /> + <Compile Include="Messaging\MessageProtectionTasks.cs" /> <Compile Include="Messaging\MultipartContentMember.cs" /> <Compile Include="Messaging\DataBagFormatterBase.cs" /> <Compile Include="Messaging\HmacAlgorithms.cs" /> diff --git a/src/DotNetOpenAuth.Core/Messaging/Channel.cs b/src/DotNetOpenAuth.Core/Messaging/Channel.cs index a134180..e36bb94 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Channel.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Channel.cs @@ -140,11 +140,6 @@ namespace DotNetOpenAuth.Messaging { private IMessageFactory messageTypeProvider; /// <summary> - /// Backing store for the <see cref="CachePolicy"/> property. - /// </summary> - private RequestCachePolicy cachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore); - - /// <summary> /// Backing field for the <see cref="MaximumIndirectMessageUrlLength"/> property. /// </summary> private int maximumIndirectMessageUrlLength = Configuration.DotNetOpenAuthSection.Messaging.MaximumIndirectMessageUrlLength; diff --git a/src/DotNetOpenAuth.Core/Messaging/MessageProtectionTasks.cs b/src/DotNetOpenAuth.Core/Messaging/MessageProtectionTasks.cs new file mode 100644 index 0000000..29163f7 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/MessageProtectionTasks.cs @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------- +// <copyright file="MessageProtectionTasks.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + /// <summary> + /// Reusable pre-completed tasks that may be returned multiple times to reduce GC pressure. + /// </summary> + internal static class MessageProtectionTasks { + /// <summary> + /// A task whose result is <c>null</c> + /// </summary> + internal static readonly Task<MessageProtections?> Null = Task.FromResult<MessageProtections?>(null); + + /// <summary> + /// A task whose result is <see cref="MessageProtections.None"/> + /// </summary> + internal static readonly Task<MessageProtections?> None = + Task.FromResult<MessageProtections?>(MessageProtections.None); + + /// <summary> + /// A task whose result is <see cref="MessageProtections.TamperProtection"/> + /// </summary> + internal static readonly Task<MessageProtections?> TamperProtection = + Task.FromResult<MessageProtections?>(MessageProtections.TamperProtection); + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs index b84bd48..a4aff73 100644 --- a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs @@ -88,6 +88,11 @@ namespace DotNetOpenAuth.Messaging { private const int SymmetricSecretHandleLength = 4; /// <summary> + /// A pre-completed task. + /// </summary> + private static readonly Task CompletedTaskField = Task.FromResult<object>(null); + + /// <summary> /// The default lifetime of a private secret. /// </summary> private static readonly TimeSpan SymmetricSecretKeyLifespan = Configuration.DotNetOpenAuthSection.Messaging.PrivateSecretMaximumAge; @@ -151,6 +156,13 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Gets a pre-completed task. + /// </summary> + internal static Task CompletedTask { + get { return CompletedTaskField; } + } + + /// <summary> /// Gets a random number generator for use on the current thread only. /// </summary> internal static Random NonCryptoRandomDataGenerator { diff --git a/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/TokenHandlingBindingElement.cs b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/TokenHandlingBindingElement.cs index 008b932..5875650 100644 --- a/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/TokenHandlingBindingElement.cs +++ b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/TokenHandlingBindingElement.cs @@ -79,13 +79,13 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// Implementations that provide message protection must honor the /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. /// </remarks> - public async Task<MessageProtections?> ProcessOutgoingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { + public Task<MessageProtections?> ProcessOutgoingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { var userAuthResponse = message as UserAuthorizationResponse; if (userAuthResponse != null && userAuthResponse.Version >= Protocol.V10a.Version) { var requestToken = this.tokenManager.GetRequestToken(userAuthResponse.RequestToken); requestToken.VerificationCode = userAuthResponse.VerificationCode; this.tokenManager.UpdateToken(requestToken); - return MessageProtections.None; + return MessageProtectionTasks.None; } // Hook to store the token and secret on its way down to the Consumer. @@ -101,10 +101,10 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { } this.tokenManager.UpdateToken(requestToken); - return MessageProtections.None; + return MessageProtectionTasks.None; } - return null; + return MessageProtectionTasks.Null; } /// <summary> @@ -125,13 +125,13 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// Implementations that provide message protection must honor the /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. /// </remarks> - public async Task<MessageProtections?> ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { + public Task<MessageProtections?> ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { var authorizedTokenRequest = message as AuthorizedTokenRequest; if (authorizedTokenRequest != null) { if (authorizedTokenRequest.Version >= Protocol.V10a.Version) { string expectedVerifier = this.tokenManager.GetRequestToken(authorizedTokenRequest.RequestToken).VerificationCode; ErrorUtilities.VerifyProtocol(string.Equals(authorizedTokenRequest.VerificationCode, expectedVerifier, StringComparison.Ordinal), OAuthStrings.IncorrectVerifier); - return MessageProtections.None; + return MessageProtectionTasks.None; } this.VerifyThrowTokenTimeToLive(authorizedTokenRequest); @@ -147,7 +147,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { this.VerifyThrowTokenNotExpired(accessResourceRequest); } - return null; + return MessageProtectionTasks.Null; } #endregion diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs index 60df47f..29ef8d5 100644 --- a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs @@ -17,17 +17,6 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// Sets the HTTP Method property on a signed message before the signing module gets to it. /// </summary> internal class OAuthHttpMethodBindingElement : IChannelBindingElement { - /// <summary> - /// A reusable pre-completed task that may be returned multiple times to reduce GC pressure. - /// </summary> - private static readonly Task<MessageProtections?> NullTask = Task.FromResult<MessageProtections?>(null); - - /// <summary> - /// A reusable pre-completed task that may be returned multiple times to reduce GC pressure. - /// </summary> - private static readonly Task<MessageProtections?> NoneTask = - Task.FromResult<MessageProtections?>(MessageProtections.None); - #region IChannelBindingElement Members /// <summary> @@ -58,13 +47,13 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { HttpDeliveryMethods transmissionMethod = oauthMessage.HttpMethods; try { oauthMessage.HttpMethod = MessagingUtilities.GetHttpVerb(transmissionMethod); - return NoneTask; + return MessageProtectionTasks.None; } catch (ArgumentException ex) { Logger.OAuth.Error("Unrecognized HttpDeliveryMethods value.", ex); - return NullTask; + return MessageProtectionTasks.Null; } } else { - return NullTask; + return MessageProtectionTasks.Null; } } @@ -82,8 +71,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// Thrown when the binding element rules indicate that this message is invalid and should /// NOT be processed. /// </exception> - public async Task<MessageProtections?> ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { - return null; + public Task<MessageProtections?> ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { + return MessageProtectionTasks.Null; } #endregion diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBase.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBase.cs index 2db5027..357ca45 100644 --- a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBase.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBase.cs @@ -86,7 +86,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// The protections (if any) that this binding element applied to the message. /// Null if this binding element did not even apply to this binding element. /// </returns> - public async Task<MessageProtections?> ProcessOutgoingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { + public Task<MessageProtections?> ProcessOutgoingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { var signedMessage = message as ITamperResistantOAuthMessage; if (signedMessage != null && this.IsMessageApplicable(signedMessage)) { if (this.SignatureCallback != null) { @@ -98,10 +98,10 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { signedMessage.SignatureMethod = this.signatureMethod; Logger.Bindings.DebugFormat("Signing {0} message using {1}.", message.GetType().Name, this.signatureMethod); signedMessage.Signature = this.GetSignature(signedMessage); - return MessageProtections.TamperProtection; + return MessageProtectionTasks.TamperProtection; } - return null; + return MessageProtectionTasks.Null; } /// <summary> @@ -114,14 +114,14 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// Null if this binding element did not even apply to this binding element. /// </returns> /// <exception cref="InvalidSignatureException">Thrown if the signature is invalid.</exception> - public async Task<MessageProtections?> ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { + public Task<MessageProtections?> ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { var signedMessage = message as ITamperResistantOAuthMessage; if (signedMessage != null && this.IsMessageApplicable(signedMessage)) { Logger.Bindings.DebugFormat("Verifying incoming {0} message signature of: {1}", message.GetType().Name, signedMessage.Signature); if (!string.Equals(signedMessage.SignatureMethod, this.signatureMethod, StringComparison.Ordinal)) { Logger.Bindings.WarnFormat("Expected signature method '{0}' but received message with a signature method of '{1}'.", this.signatureMethod, signedMessage.SignatureMethod); - return MessageProtections.None; + return MessageProtectionTasks.None; } if (this.SignatureCallback != null) { @@ -135,10 +135,10 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { throw new InvalidSignatureException(message); } - return MessageProtections.TamperProtection; + return MessageProtectionTasks.TamperProtection; } - return null; + return MessageProtectionTasks.Null; } #endregion diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs index 25720f6..ae7aa8b 100644 --- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs @@ -91,7 +91,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// Implementations that provide message protection must honor the /// <see cref="MessagePartAttribute.RequiredProtection" /> properties where applicable. /// </remarks> - public override async Task<MessageProtections?> ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { + public override Task<MessageProtections?> ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { bool applied = false; // Check that the client secret is correct for client authenticated messages. @@ -204,7 +204,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { } } - return applied ? (MessageProtections?)MessageProtections.None : null; + return applied ? MessageProtectionTasks.None : MessageProtectionTasks.Null; } } } diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/TokenCodeSerializationBindingElement.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/TokenCodeSerializationBindingElement.cs index 756ef4f..938e587 100644 --- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/TokenCodeSerializationBindingElement.cs +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/TokenCodeSerializationBindingElement.cs @@ -48,7 +48,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// Implementations that provide message protection must honor the /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. /// </remarks> - public override async Task<MessageProtections?> ProcessOutgoingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { + public override Task<MessageProtections?> ProcessOutgoingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { var directResponse = message as IDirectResponseProtocolMessage; var request = directResponse != null ? directResponse.OriginatingRequest as IAccessTokenRequestInternal : null; @@ -58,7 +58,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { var codeFormatter = AuthorizationCode.CreateFormatter(this.AuthorizationServer); var code = authCodeCarrier.AuthorizationDescription; authCodeCarrier.Code = codeFormatter.Serialize(code); - return MessageProtections.None; + return MessageProtectionTasks.None; } // Serialize the refresh token, if applicable. @@ -77,7 +77,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { accessTokenResponse.AccessToken = accessTokenResponse.AuthorizationDescription.Serialize(); } - return null; + return MessageProtectionTasks.Null; } /// <summary> @@ -102,7 +102,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "incorrectclientcredentials", Justification = "Protocol requirement")] [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "authorizationexpired", Justification = "Protocol requirement")] [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "DotNetOpenAuth.Messaging.ErrorUtilities.VerifyProtocol(System.Boolean,System.String,System.Object[])", Justification = "Protocol requirement")] - public override async Task<MessageProtections?> ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { + public override Task<MessageProtections?> ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { var authCodeCarrier = message as IAuthorizationCodeCarryingRequest; if (authCodeCarrier != null) { var authorizationCodeFormatter = AuthorizationCode.CreateFormatter(this.AuthorizationServer); @@ -119,7 +119,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { refreshTokenCarrier.AuthorizationDescription = refreshToken; } - return null; + return MessageProtectionTasks.Null; } } } diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs index 676b248..62dc311 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs @@ -66,7 +66,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// The <see cref="HttpWebRequest"/> prepared to send the request. /// </returns> /// <remarks> - /// This method must be overridden by a derived class, unless the <see cref="Channel.RequestCore"/> method + /// This method must be overridden by a derived class, unless the <see cref="Channel.RequestCoreAsync"/> method /// is overridden and does not require this method. /// </remarks> protected override HttpRequestMessage CreateHttpRequest(IDirectedProtocolMessage request) { diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/OpenIdProvider.cs index b89aeca..e71c291 100644 --- a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/OpenIdProvider.cs +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/OpenIdProvider.cs @@ -338,7 +338,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <param name="cancellationToken">The cancellation token.</param> /// <param name="extensions">The extensions.</param> /// <returns> - /// A <see cref="OutgoingWebResponse" /> object describing the HTTP response to send + /// A <see cref="HttpResponseMessage" /> object describing the HTTP response to send /// the user agent to allow the redirect with assertion to happen. /// </returns> public async Task<HttpResponseMessage> PrepareUnsolicitedAssertionAsync(Uri providerEndpoint, Realm relyingPartyRealm, Identifier claimedIdentifier, Identifier localIdentifier, CancellationToken cancellationToken = default(CancellationToken), params IExtensionMessage[] extensions) { diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cs index ad20356..3a3b430 100644 --- a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cs +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cs @@ -606,7 +606,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <typeparam name="T">The extension <i>response</i> type that will read data from the assertion.</typeparam> /// <param name="propertyName">The property name on the openid_identifier input box object that will be used to store the extension data. For example: sreg</param> /// <remarks> - /// This method should be called before <see cref="ProcessResponseFromPopupAsync()"/>. + /// This method should be called before <see cref="ProcessResponseFromPopupAsync(CancellationToken)"/>. /// </remarks> [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "By design")] public void RegisterClientScriptExtension<T>(string propertyName) where T : IClientScriptExtensionResponse { |